printf.exe - User's Manual

The term computer programming is usually associated with the creation of video-games; however, this term encompasses a wide range of applications in several areas, although in all cases this implies the use of a programming language. Learning to use a programming language requires some technical knowledge and/or skills that some people just don't have, perhaps due to their area of experience or age.

There are several tools designed to "teach programming" to inexperienced people (some of them aimed at children); however, these tools generally do not provide the elements required in a further, more formal study of a conventional programming language. Part of the problem is that these tools focus on the "fun" aspect of learning, so they include a large number of graphics, sounds, animations, etc. whose elements are assembled visually as if it were a game, but neglecting the more technical aspects of this knowledge. Don't forget that creating a program that works correctly doesn't have to be "fun"; it represents a major technical challenge.

A simpler tool to learn and apply would be one without games, sounds or animations, but that works based on commands/orders/instructions in the same way as an imperative programming language. This tool can be specifically designed with a minimum of rules and features, but still providing the same concepts and way of applying them as traditional programming languages; this would allow a greater number of people who are not normally able to access conventional methods to learn programming to do so.

printf.exe is a console application for Windows that, in its most basic form, allows you to display messages and generate results of arithmetic operations that appear in a certain format. The formatted results are displayed using the well-known printf function of the C programming language, of which there are many examples of use in various places on the web. The arithmetic operations are evaluated using a simple notation that fits perfectly with the way printf is used, making it a natural complement to that function. This notation (RPN) was taken from a type of calculator (HP) that has traditionally been used as a basic programming tool aimed at people with no experience (usually high school or middle school students) of which there are many examples of use on the Internet. In this way, the core of the printf.exe tool is based on two pillars that have been widely known in the field of programming for many years: the printf C language function and HP calculators.

The practice of the RPN notation of HP calculators is specially valuable in the learning of good habits for computer programming. There is a large amount of opinions in the web about "RPN teachs the brain to think correctly". At the HP Museum you can read: RPN is faster and easier, and it actually TRAIN mathematical thinking and problem solving better, and in this site appears this opinion: Using an RPN calculator can be a valuable learning experience that can help you develop a deeper understanding of mathematical concepts and improve your problem-solving skills among other similar opinions. printf.exe takes on these aspects and expands on them, also teaching the user to be meticulous and pay attention to details in order to avoid errors. These abilities certainly won't be learned by playing video games! The "natural" way of using printf.exe encourages to planning first, then do: The more complex the issue, the more planning required if you want to have the optimal chance of success. All these skills will be valuable tools for the future success of an excellent computer programmer.

printf.exe also features the simplest programming scheme ever created, similar to that of HP calculators, of which there are literally thousands of examples on the web, since this type of programs has been created and published by the community for more than 50 years. This point allows the user to take a first contact with programming almost as easily as learning to perform arithmetic operations in the HP calculator. The programming scheme in printf.exe is based on a programming language (REC) derived from Lisp that was one of the precursors of structured programming; the REC language has been successfully used in teaching mathematic logic for several years. This programming scheme requires just four elements, and the same "Do-if-true" HP calculator rule about evaluating conditions, to assemble all classical figures of structured programming. This represents a very simple method to understand, yet it provides the same insights that are required while using a modern structured programming language.

In 1967 the Logo programming language was designed with didactic purposes. The most remarkable aspect of Logo is its "Turtle Graphics" feature because it provides an important visual impact in the students that encourages they to split large problems in small pieces even if they know nothing about programming. One of the example programs included in printf.exe package is a Logo Turtle Graphics emulation. In this way, the persons that are not oriented to mathematics can use this program to take a first contact with computer programming with a more visual, less technical perpective, and later learn the programming methods that allows they to draw more interesting graphics. This is the same learning method that hundreds of thousands of people have used for over 50 years. It is interesting to mention that the programming functions of printf.exe are simpler and "more standard" than those of Logo.

The extensive documentation does not assume any prior knowledge of these topics so the examples are straightforward and the explanations are detailed. The user's manual starts with basic explanations and progresses to more and more in-depth topics as advanced features of this tool are introduced. In this way the printf.exe application can be used as an introduction to computer programming that does not require a specialized knowledge base on the part of the user.

printf.exe = printf + RPN + REC + Logo The best of four worlds for an easy but effective programming learning!

For a better experience, it is suggested to register your copy of printf.exe program.

Table of Contents

  1. Introduction
  2. Show Formatted Output
  3. Arithmetic and String Operations
    1. Data Types
    2. Integer Operations
    3. Floating Point Operations
    4. Storage Registers
    5. Stack Management
    6. FPU Stack Management
    7. String Operations
    8. Character Pointers
  4. Block Programming
    1. Input/Output Operations
    2. Code Block
    3. Control Flow Transfer
    4. Test/Condition
    5. Basic Conditional Tests
    6. Program in Didactic Form (Batch files)
    7. Flags Tests
    8. Standard Numeric Tests
    9. Advanced Input/Output Operations
    10. Named Code Blocks (subroutines)
    11. Boolean Operators
  5. Advanced Graphical Examples
    1. The Mandelbrot Set
    2. Turtle Graphics (Logo)
      1. Recall and edit command lines
    3. Turtle Graphics Labels
    4. Recursive curves
  6. Appendices
    1. printf.exe installation and support
    2. Increasing storage space
    3. Error Messages

Introduction

printf.exe is a console application that is used in text mode at the cmd.exe Windows command prompt, that is, it is not a Graphical User Interface program. The application is written in assembly language, so it is very small and efficient. You can use printf.exe to generate simple output on the command line or advanced math output in Batch files, or even write complete small to medium-sized applications using printf.exe's scripting capabilities in a much easier way than using any modern programming language.

This application don't requires a complicated installation: just follow the instructions at Appendix 1 to download in your computer the printf.zip package file, and extract all included files in a given folder. Then, open the Command Prompt (right-click the Start button and select "Command Prompt"); the black cmd.exe window should appear. Change current directory to printf.exe's one: type CD followed by a space, then drag and drop printf.exe folder into cmd.exe window and press Enter. You can now use printf.exe!

NOTE: Some antivirus applications flags printf.exe as "potentially dangerous" or "positively infected". This is a false positive message caused mainly because the application is written in assembly language, something unusual these days. If you downloaded the printf.zip package file from apaacini.com site, then it is virus free for sure. The first time that you execute this program, Windows flags it with a warning message about the risk of executing programs downloaded from the web. Just answer: "Execute it anyway".

The general syntax to use printf.exe program is this:

printf "format string" data1 data2 data3 ...

In it's simplest form, the format string contains a series of characters that will be displayed in the screen. For example:

printf "Hello, world"

You can copy anyone of the examples shown here (selecting it by pressing the mouse left button while move the mouse, and press Ctrl-C) and paste it in the cmd.exe window via a mouse right-click or press Ctrl-V; if this not works, right-click on the window title bar and select "Edit" -> "Paste". When the command appears in the screen, press Enter to execute it.

The formatted output task in printf.exe program is achieved via the Windows API function of the same name. The program just take the parameters provided by the user and pass they to the C Run-Time printf function with no further checking. This means that it is up to the user to fulfill the requirements of printf function; otherwise the same errors or failures described in printf documentation will occur.

The arithmetic operations are performed in the same data area (stack) used for printf parameters, so the operations are evaluated using the simplest arithmetic scheme: Reverse Polish Notation, also called RPN or postfix notation. This method allows printf.exe program be relatively simple and the operations be performed in a very efficient way, but this point also means (again) that it is up to the user to correctly provide operations that follow the rules of RPN expressions.

The above two paragraphs means that printf.exe is a program somewhat difficult to use! However, the operation rules are not complicated; you just need to be meticulous and pay attention to details. The ultimate reward for this effort is that you will obtain numeric results (and text-processing ones) in a much simpler way than using any programming language, with the same or better precision, and much faster! On the other hand, you will learn to locate and fix errors even if they are caused by minimal faults.
Top

Show Formatted Output

The format string contains ordinary characters or certain control characters preceded by a \ backslash (escape sequences), and data conversion format specifications designed to show data, that start in % percent-sign and end in a letter.

The escaped control characters allowed in printf.exe are: \n new line LineFeed ASCII 10 (output as CR+LF in Windows), \r CarriageReturn ASCII 13, \t TABulation ASCII 9, \b BackSpace ASCII 8, and \a BELl ASCII 7. Any other character placed after the backslash is textually inserted; for example, use \\ to insert a backslash or \" to insert a quote:

printf "She said: \"Goodbye!\" and gone\n"

The format string can be the name of a Batch variable, so printf.exe uses such stored string. However, you can not directly insert control characters in a Batch variable using \escaped letters; you must use the method described at printf Example 0 - Operations.bat file.

Format specifications designed to show data start with a % percent-sign character and end in a letter that depends on the type of the data: %c for characters, %s for strings, %i for integer numbers and %f for floating point numbers.

Data placed after the format string must be separated by space or TAB characters. The type of this data depends on the way it is written: a character is enclosed in apostrophes, a string is enclosed in quotes, an integer number have no decimal point, and a floating point number have decimal point. In this way, each %letter specification in the format string is matched with a data, that must be of the same type. For example:

printf "A character: %c\tA string: %s\tAn integer: %i\tA floating point: %f\n"  'X'  "ABC"  1  1.

You can also use as data a Batch environment variable that is taken as a string. For example:

printf "%s\n" PATH

Note that data elements are separated by one or more spaces (or TAB characters). If there are more data than format specifications, extra data is not displayed. If there are more format specifications than data, garbage is shown or a run-time error will occur. The C printf function documentation indicate: "The results are undefined if there are not enough arguments for all the format specifications". In this phrase "undefined" means that anything may happen.

A very important point you should pay attention is that printf.exe program can be characterized as an untyped application (like Assembly language): it is up to the programmer to ensure that data given to functions is of the appropriate type. If the types of specification and data don't match, garbage is shown or a run-time error will occur. The only format vs data type mismatch allowed is use %i on a character (that shows its ASCII code) and use %c on an integer (that show such a character); this happen because characters are internally managed as 32-bits integers. For example:

printf "The ASCII code of '%c' is %i. The char of code %i is '%c'\n"  'A' 'A'   97 97

Previous line display: The ASCII code of 'A' is 65. The char of code 97 is 'a'

Each %letter format specification allows a major control in the way the data is displayed. The general syntax of the data format specification is described below.

Data format specification: %[flags][width][.precision]type

flags     type
-+ 0#     cdiouxXeEfgGaAps
- left align c Character
+ Insert + sign if positive d integer (Decimal)
" " Insert space for + sign i Integer
0 zero pad o integer (Octal)
# insert . in g type, or
insert 0|0x in o|x types
u integer (Unsigned)
  x integer (hexadecimal)
X integer (Hexadecimal)
e double ([-]d.dddddde∓ddd)
E double (like e, with E)
f double ([-]ddd.dddddd)
g double (shorter of f|e)
G double (like g, with E)
a double ([-]0xh.hhhp∓ddd)
A double (like a, with X and P)
p string (address in hex)
s String
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

flags is an optional part that adds certain characters to the output, usually at left side of the field.

width is a number that specifies the minimum or total width of the field used to display the data. However, this width will not truncate the value, so the field is incremented if necessary. printf "%04i" 12 show: 0012   printf "%04i" 12345 show: 12345

.precision is a number that specifies the maximum width of its part, so the value will be truncated and rounded if necessary. printf "%.4f" 3.141592654 show 3.1416

When floating point numbers are shown, width specifies the "number of columns" of the field and .precision the "number of decimals", but this interpretation differ with other data types, like integers. For example, in %.12s string format, the string value is cut at 12 characters.

A string is the only type of value that will be supressed if the precision is zero: %.0s. As a particular case, if the precision is zero and the integer value is zero: printf "%.0i" 0, nothing is shown.

The type letter must match the type of the value accordingly to the table at left side. In this table the "double" term refers to the 64-bits floating point numbers managed in printf.exe program.

For further details, you can review the complete description of format specifications at this site.

Note: if you use printf.exe program in a Batch (.bat extension) file, all percent-signs must be doubled. For example, this command-prompt line:

printf "An integer:%i\nA float: %f\n"  123  456.789

... must be written in a Batch file this way:

printf "An integer:%%i\nA float: %%f\n"  123  456.789

Advanced topic: in a format specification the width and precision values can be an asterisk that indicate to take the next integer data and use it in place of the asterisk. For example, in printf "%0*i" 5 3 the 5 takes the place of the asterisk, so the output is the same as in printf "%05i" 3: 00003. This example printf "%0*.*f" 8 4 3.141592654 show the same output than this one printf "%08.4f" 3.141592654: 003.1416.

The advantage of this feature is that width and precision values can be dynamically calculated when printf.exe execute. If the precision value is zero in a string format specification, the string data is just not shown. For example:

printf "The result is: %.*s %.*s \n" 14 "yes, of course" 0 "no, sorry..." show The result is: yes, of course, but

printf "The result is: %.*s %.*s \n" 0 "yes, of course" 12 "no, sorry..." show The result is: no, sorry....

One of the 14 or 12 values can be changed to zero depending on a selector value, so this feature allows to conditionally select one of two messages. Of course, the selection can be made from any number of different strings!
Top

Arithmetic and String Operations

Before describe how to perform arithmetic operations in printf.exe program you must be aware of an important aspect about numbers. As said before, printf.exe program is an untyped application and in such applications there are not any implicit type conversions: you must always provide the correct type of values or explicitly perform any required type conversion. In printf.exe application (and in practically all programming languages) there are two different types of numbers: integer and floating point. This means that, in printf.exe (unlike in other languages) there are also two different operator sets for evaluating operations on integer and floating point numbers (in the exact same way that there are two different data formats to display %i integer and %f floating point numbers). Integer operators are special characters (like + - * / etc), and three-letter words are used as operators on floating point numbers (like ADD SUB MUL DIV etc). If you use the wrong operator type, or if you try to operate two numbers of different types, you will get garbage as result or even an unrecoverable run-time error (in the exact same way as if you use the wrong format specification type to show numbers). Everytime you don't get the expected result from printf.exe application, check in first place the type of the numbers, operators and formats used. Let's see a couple simple examples: this code printf "An integer show with F format: %f\n" 123 show An integer show with F format: 0.000000 and this one printf "A floating point show with I format: %i\n" 1.2 show A floating point show with I format: 858993459. If you understand this point, you could avoid the most frequent problem about the use of printf.exe application.

This program uses a method to evaluate arithmetic operations different than the usual algebraic notation. The method is called Reverse Polish Notation (RPN) and was choosen because it is simpler to implement (and use) than algebraic notation. In standard algebraic notation, multiplication and division have major precedence (are evaluated before) than addition and subtraction. This means that when several operations are combined together, the operations with greater precedence are executed first (from left to right) and then the operations with lower precedence are completed (from left to right). For example, the following algebraic expression:

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

... imply to first add 4+5, store such 9 partial result and keep pending the subtract operation for later. Then multiply 6*7 and divide the result by 8. And now get the stored 9 to complete the pending subtract operation, and finally add 9. If you want to change this standard order, you need to insert parentheses to enclose the operations that must be performed first. Taking into account that parentheses can be nested several levels deep and that there are other operations with different precedences, such as exponentiation (higher precedence than *) or bitwise operations (lower precedence than +), and that there are a few operators that execute from right to left (like all one-operand operators) it is easy to understand that the evaluation of a large and complicated algebraic expression can have several pending operations and partial results that must be completed in a convoluted way. When a large expression is required in a computer program, it is common that the programmer split the (algebraic) expression into several simpler subexpressions in order to increase clarity. This is just absurd...

(There is a programming language, called APL, which has many (over 50!) different operators. In order to avoid complicated precedence rules on such a large number of operators, the designers of APL opted for a simple rule: there are no precedences in an expression, so all operators execute from right to left. For example: A = 3 * 4 + 5 means to first add 4 + 5 and then multiply the 3 by 9. This leads to writing APL expressions that usually have parentheses on the left side.)

RPN is different. In RPN there are not "operators precedence" nor "pending operations". All operations in RPN follow a simple rule: the operation is performed as soon as an operator appears; this means that the numbers must be introduced before. RPN operations are executed in a more efficient way than algebraic ones. It is interesting to mention that all algebraic calculators, as well as programming languages that use algebraic expressions, first convert them to RPN to execute operations in a simpler way. The method used to perform this conversion is explained in this article. You can take a first contact with RPN at HP Museum site or read a more detailed explanation in this extensive tutorial. You are invited to take a look at anyone of these RPN (HP) sites before proceeding, but be aware that the use of RPN in printf.exe program is simpler than in the HP calculator because here the numbers are separated by just a space.

Just remember that RPN calculators perform mathematical operations immediately when you enter the operator so the number(s) must be entered first. Note that in printf.exe the numbers are separated via a space and that there is no equivalent of Enter↑ nor CLx HP keys (neither the special cases of their "stack lift state"). You can review the proper conversion of algebraic expressions into RPN ones in the AlgebraicToRPN.bat program included in the printf.zip package; just execute it and enter proper algebraic expressions (the conversion program does not catch errors in the given algebraic expressions).

Let's review a simple example of a RPN addition operation in printf.exe:

printf "A number: %i, another number: %i\n"  3 4
printf "The sum of 3 plus 4 is: %i\n"  3 4 +

In first example there are two integer numbers and two integer formats, so everything is correct.

In second example there are two integer numbers. After they, there is an integer RPN operator that process the two previous numbers and produce a single result, so finally there is a single integer result and a single integer format: correct!

Using this scheme you can evaluate any RPN expression, no matter how long it could be; just be sure that integer numbers are operated with integer operators, and floating point numbers use floating point (three-letters) operators. Of course, check also that integer results are displayed with %i format, and floating point results use %f format. These rules are not too hard to fulfill, isn't it? ;)

printf "The sum of 3 plus 4 is %i\nThe sum of 3. plus 4. is %f\n"  3 4 +  3. 4. ADD

Let's complete previous example of algebraic expression:

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

Note that operations and partial results are evaluated and completed in the same order than before; this is obvious if we want to get the same result. However, in this case we are explicitly stating the order of operations! They are not governed by precedence rules that are not apparent in the expression itself.

Note also that the 13 result differ from the correct 12.75. This is caused by integer operations (that chops any fractional part in division) and the order of operations. To get the right result, use floating point numbers:

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

You can review a lot of additional operations examples in printf Example 0 - Operations.bat file.

Most printf.exe arithmetic operators works the same as they do in HP RPN calculators so they will not be explained here, just the operations that have not a counterpart in HP calculators. If you have any doubt about HP calculator operations, review any HP user's manual like the HP-15C one (PDF 3.75 MB).

Note: HP calculators descriptions specify that you can keep up to four partial results in RPN expressions. In printf.exe program you have no limit of partial results for integer numbers and up to 8 partial results for floating point numbers. This point will be further explained later.

Data Types

printf "format string" {number [operator]|'c'|"string"|variable} ...

The parameters for printf.exe program after the first one can be of anyone of the following types:

Type Example Description
Character 'X' Single character enclosed in apostrophes. Characters are internally managed as 32-bits numbers.
String "Hello" Several characters enclosed in quotes. Strings are internally managed as 32-bits addresses.
Integer 3 Number with no decimal point, with an optional negative sign. Can be written as an hexadecimal number that start with 0x, or an octal number that start with (left) zero (in such a case 8 and 9 are invalid digits). Integers are internally managed as 32-bits numbers with a range of values from -2147483648 to 2147483647 (or from 0 to unsigned 4294967295, %u format).
Floating
Point
5. Number with decimal point or written in standard scientific (E) notation, with a maximum of 18 significant digits and a (normalized) exponent of ten from -320 to +308. Floating point numbers are internally managed as 64-bits double numbers.
String
Variable
Varname Any name that start with letter that is not an internal function. Its value is the string stored in such a Batch variable.

A number start with digit, or optionally with a minus sign. The general format for numbers is: [-]digits[.[digits]][{E|e}[+|-]digits]. Square brackets [ ] surround optional elements. Curly braces and a vertical bar { | } surround alternatives for a single element. If the number include a decimal point or an E, it is a floating point number; otherwise it is an integer. For example:

printf "A character: %c\tA string: %s\tAn integer: %i\tA floating point: %f\n"  'X'  "ABC"  1  1.

Characters are stored as 32-bits numbers. This point allows to achieve these "tricks":

Strings are managed in the printf parameters as the 32-bits address of the first character in the string. This point will be further discussed later, in String Operations.

Integer Operations

Several integer operators use special characters, like < > | &, that must be "^escaped" this way: ^< ^> ^| ^& when they are used in the command prompt. In order to facilitate the entry of such operators, you can insert the special "Quoted" switch /" before the special characters. If after this switch there is a string that contains these special characters, you should "close" the Quoted switch by putting it again. For example:

printf "Two numbers: %i %i and a string: %s\n" 12 34  /"  <>  /"  "<Hello>"
Integer Operators
Unary operators ! BoolNot   ~ BitNot   _ Change Sign   $ Signum   ++ Increment   -- Decrement
Two-operands
operators
Basic + Add   - Subtract   * Multiply   / Quotient   % Remainder   ** Power
Bitwise << BitSHL   >> BitSHR   & BitAnd   | BitOr   ^ BitXor
Special operators # Random   ] Store   [ Recall   . Float   /* Comment */
Stack management > Dup   <> Exchange   < Drop   { Roll Down   } Roll Up

Note that several operators are comprised of two or more characters, so you always should separate complete operations via one or more spaces or TAB characters. For example: <> is Exchange, so in order to write a Drop followed by a Dup, you must separate they this way: < >.

In these five operators: * / % << >> you can add a lowercase u letter at end to specify "unsigned operation"; for example: >>u.

** (Power, yx in HP calculators) is the only integer two-operands operator that is not implemented via a native CPU instruction, it uses a multiplication loop.

# (Random) operator generate a 32-bits random number less than 2147483647 and greater than zero.

. (Float) operator converts an integer number into a floating point one. After the conversion, you must operate and display the number using floating point operators and format.

{n (Roll Down) and }n (Roll Up) operators requires a position digit; they will be described below.

/* and */ (Comment) delimiters allows to insert any descriptive text that will be ignored.

Floating Point Operations

All floating point operations are specified via a word of a maximum of 4 characters, that we call functions.

Floating Point Functions
Unary
operators
Basic CHS ABS SIGN FRAC INV SQR SQRT
Trigonometric SIN COS TAN ASIN ACOS ATAN DEG RAD
Logarithms LN LOG EXP EXPT
Two-operands operators ADD SUB MUL DIV MOD POW
Special operators ZERO ONE PI STO RCL INT
Stack management DUP XCHG DROP RDN RUP REMOVE LOAD

In HP calculators: INV is 1/x, SQR is x2, SQRT is √x̅, EXP is ex, EXPT is 10x and POW is yx.

Trigonometric functions works in radians always. DEG converts radians to degrees; RAD converts degrees to radians.

ZERO, ONE and PI are handy functions that load the 0.0, 1.0 and Π (3.141592654...) constants via native FPU operations.

INT function converts a floating point number into an integer one (not just chop the fractional part). After the conversion, you must operate and display the number using integer operators and format. If you want the integer part of a floating point value, just do this: DUP FRAC SUB.

DROP:* function drops all registers in the X87 Floating Point Unit (FPU) preserving printf.exe parameters. REMOVE function removes the parameters of printf.exe preserving FPU stack. LOAD function load in the printf.exe parameters the values taken from FPU stack. See description of these functions below.

The X87 Floating Point Unit is capable of manage certain Special Values (like minus zero or infinity) as the result of specific operations. For example: printf "Minus zero: %g Infinite: %g\n" -1. 0. MUL 1. 0. DIV show:
Minus zero: -0 Infinite: 1.#INF
For a further explanation of this and other aspects of FPU operation, see this Wikipedia article.

Storage Registers

Initial Configuration
N i Int 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

Besides the numbers you can insert as parameters for printf.exe, there are also several data storage registers that allows you to store and recall both integer and floating point numbers in order to be reused in a posterior part of the RPN expression. You can also perform basic arithmetic operations over a storage register (store arithmetic), or using a storage register to perform arithmetic operations over last entered number (recall arithmetic). You can read an introduction to the use of storage registers at page 42 of HP-15C Owner's Handbook.

As shipped, printf.exe application can manage 20 integer storage registers and 20 floating point storage registers (apart from I Index Register) as shown in the "initial configuration" at right side. The 20 registers of both types can be directly addressed via a 0..9 digit inserted in place of N letter in the storage registers operations below, or via the point-digit combination that address registers 10 to 19; for example: ]3 or STO.5.

The number of available storage registers (integer and float) and the size of the area reserved to store character strings can be increased through the procedure described in Appendix 2. When there are more than 20 storage registers, the additional registers must be indirectly addressed through the i Index Register as explained in Section 10: The Index Register of the HP-15C Owner's Handbook, that could be translated to printf.exe this way: The Index register is an integer storage register that can be used directly, with I like in [I, or indirectly, with i like in [i. The I function uses the number itself in the Index register. The i function uses the number in the Index register to address another data storage register, integer or float. This is called indirect addressing.

IMPORTANT: Note that the use of "I" and "i" letters to access the Index Register this way is the only printf.exe operation that is case-aware. You should pay attention to this point and insert the appropriate letter accordingly to the desired operation.

Storage Registers Operations
  Int Float Name Operation Description
S
T
O
R
E
]n STOn Store RegN = X Store X in storage register N
][n STO[n Store-Xchg RegN <=> X Exchange X and storage register N
]+n STO+n Store-Add RegN = RegN+X Add X to storage register N
]++n STO++n Store-Inc RegN = RegN+1 Increment storage register N
]-n STO-n Store-Sub RegN = RegN-X Subtract X from storage register N
]--n STO--n Store-Dec RegN = RegN-1 Decrement storage register N
]*n STO*n Store-Mul RegN = RegN*X Multiply storage register N by X
]/n STO/n Store-Div RegN = RegN/X Divide storage register N by X
   
R
E
C
A
L
L
[n RCLn Recall Push RegN Recall X from storage register N
[+n RCL+n Recall-Add X = X+RegN Add to X the storage register N
[++n RCL++n Recall-Inc X = X+1 Increment X
[-n RCL-n Recall-Sub X = X-RegN Subtract from X the storage register N
[--n RCL--n Recall-Dec X = X-1 Decrement X
[*n RCL*n Recall-Mul X = X*RegN Multiply X by storage register N
[/n RCL/n Recall-Div X = X/RegN Divide X by storage register N

In the table above, X refers to the last entered number. As usual, the last entered number in Store operations must be of the same type of the operation, that is, an integer for ] operations or a floating point for STO operations.

IMPORTANT: All floating point Store Arithmetic operations are implemented via a 3 FPU instructions procedure and requires one stack register free. These operations will fail if there are 8 floating point numbers entered in the printf.exe parameters. Floating point Recall Arithmetic operations just uses one direct FPU instruction.

Note that [++n and [--n integer "Recall" arithmetic operations works the same as the simpler ++ (Increment) and -- (Decrement) ones, so the latter are preferred.

Most HP calculators, like the HP-15C, include functions that performs statistical calculations over two variables. To do that, each X-Y value pair is entered and the ∑+ key is pressed, so statistics values are compiled in registers R2 through R7 as described in Page 49 of the HP-15C Owner's Handbook:

Register Value Description
R2 n Number of data points accumulated.
R3 ∑x Summation of x-values.
R4 ∑x^2 Summation of squares of x-values.
R5 ∑y Summation of y-values.
R6 ∑y^2 Summation of squares of y-values.
R7 ∑xy Summation of products of x- and y-values.

The printf.exe package includes an example that performs the same statistics calculations of the HP-15C. This is a segment of such an example that compiles the statistics values in the specified registers:

		/*    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	*/ ^

The complete program is in printf Example 1 - Two var statistics.bat file.

Advanced topic: when indirect addressing is used, it is possible to increment or decrement the value of the I register after ("post") the store/recall operation was completed (post-increment/decrement). To do that, insert ++ or -- characters after the i letter. For example, the operation ]i++ store integer X in the register addressed by I and increment the value of I. RCL*i-- multiply floating point X by the register addressed by I and decrement the value of I. The way to make good use of this feature will be clearer later, in Block Programming section.

Stack Management

In this example: printf "%i %i %i %i\n" 10 20 30 40 the numbers 10, 20, 30 and 40 are entered in the same order: the first number is 10 and the last number is 40. If after that you drop one number with < the dropped number is the last one: the 40. This operational scheme (called LIFO: Last In First Out, in contrast to a queue: First In First Out or FIFO) assemble a data structure called stack. In this way, the stack management operations allow us to manipulate and change the order of the numbers entered as parameters for printf.exe program.

By default, stack management operations affect the last entered number. However, these operations also allows to affect another number that is not the last one. The affected number is indicated via a one-digit position number placed after the operator that enumerate the entered numbers from the last one, with position 1, up to the first one (backwards order). For example: <2 eliminate the last-but-one number. See more examples in table below.

Stack Management Operations
Type Oper Name Description Example
Before -->
6   5   4   3   2   1
oper --> After
 
I
N
T
E
G
E
R
> Dup Duplicate a number 10 20 30 40 >
>3
>4
10 20 30 40 40
10 20 30 40 20
10 20 30 40 10
<> Exchange Exchange last and another number 10 20 30 40 <>
<>2
<>4
10 20 40 30
10 20 40 30
40 20 30 10
< Drop Drop (eliminate) a number 10 20 30 40 <
<2
<3
<*
10 20 30
10 20 40
10 30 40
 
{ Roll Down Rotate X towards a previous position 10 20 30 40 50 60 {4
{6
10 20 60 30 40 50
60 10 20 30 40 50
} Roll Up Rotate a previous position towards X 10 20 30 40 50 60 }3
}5
10 20 30 50 60 40
10 30 40 50 60 20
F
L
O
A
T
DUP Dup Duplicate a number 10. 20. 30. 40. DUP
DUP2
DUP3
10. 20. 30. 40. 40.
10. 20. 30. 40. 30.
10. 20. 30. 40. 20.
XCHG Exchange Exchange last and another number 10. 20. 30. 40. XCHG
XCHG3
XCHG4
10. 20. 40. 30.
10. 40. 30. 20.
40. 20. 30. 10.
DROP Drop Drop (eliminate) a number 10. 20. 30. 40. DROP
DROP3
DROP4
DROP*
10. 20. 30.
10. 30. 40.
20. 30. 40.
 
RDN Roll Down Rotate X towards a previous position 10. 20. 30. 40. 50. RDN3
RDN5
10. 20. 50. 30. 40.
50. 10. 20. 30. 40.
RUP Roll Up Rotate a previous position towards X 10. 20. 30. 40. 50. RUP3
RUP4
10. 20. 40. 50. 30.
10. 30. 40. 50. 20.

Note that >1/DUP1 is the same as >/DUP, that <1/DROP1 is the same as </DROP, and that <>2/XCHG2 is the same as <>/XCHG; the <>1/XCHG1 operation is not valid.

Both <* and DROP* (Drop All) operations eliminate all data inserted after the format string. The difference between them consists in release or preserve the strings and will be explained later.

The { }/RDN RUP (Roll Down/Roll Up) operators can not be used without a position and such a position must be greater or equal 2 (like Exchange). Roll Down {/RDN move last number to a previous position (similar to store). Roll Up }/RUP move number in previous position into last number (similar to recall).

Remember that printf.exe can only keep up to 8 floating point numbers in the FPU stack; this means that the position in DUP must be in 1..7 range, in XCHG must be in 2..8 range, in DROP in 1..8 range, and in RDN/RUP in 2..8 range.

If a position refers to a non-existent stack register, printf.exe program will crash (as usual).

Advanced topic: all stack management operations assumes that all numbers in the stack are of the same type: integer or float. If there are mixed numbers of different types, the result will depends on the position of the non-of-same-type number. Floating point numbers uses 64 bits whereas integer number uses 32 bits; this means that a float number is equivalent to two integers, and that one integer is equivalent to half float. If you are aware of this situation, you can manipulate mixed types numbers in the stack and still get correct results.

When reviewing the following examples it is suggested to draw a small schematic where integers occupy "1 place" and floats occupy "2 places". Remember that integer operations move one place and float operations move two.

printf "Int: %i, Int: %i, Float: %.2f\n"  10 20 30.  /* Standard use */

Int: 10, Int: 20, Float: 30.00

printf "Int: %i, Float: %.2f, Int: %i\n"  10 20 30.
       /* Wrong: the 20 and (lower) half of 30. are shown as Float 0.00
          and the (upper) half of 30. is shown as Integer 1077805056 */

Int: 10, Float: 0.00, Int: 1077805056

printf "Int: %i, Float: %.2f, Int: %i\n"  10 20 30. }3
       /* Fixed: rotate the *THIRD* integer (the 3rd "one place" number: the 20)
          so the integer 20 is moved to the last position */

Int: 10, Float: 30.00, Int: 20

printf "Float: %.2f, Int:%i, Int: %i\n"  10 20 30. {4 {4
       /* Rotate the *two halves* of the 30. to the last position */

Float: 30.00, Int:10, Int: 20

However, this type of movement does not work with floating point functions because in this case the FPU stack registers are always involved in the operation and such registers does not match the printf.exe parameters contents if there are intermixed integer numbers. The explanation of this point is given next.

FPU Stack Management

Advanced topic: This section contains several advanced technicall descriptions.

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=
 
 
 
 
 
 
 
 
 
 

The printf.exe program uses the Floating Point Unit (FPU) to evaluate floating point arithmetic operations; this is a part of the computer hardware specifically designed to do so. The FPU uses a stack of 8 registers entirely similar to the stack of 4 registers of HP calculators. These registers can be named using the same "upside down" HP order as shown at left side (using the "ABCD" WP 34S (PDF 3 MB) calculator nomenclature for the 4 additional registers).

When printf.exe program gets a floating point number in the parameters it pushes the same number in the FPU stack. Similarly, when a result is calculated in the FPU X register it is stored back in the printf.exe parameters. In this way, both areas (printf.exe parameters and FPU stack) usually contains the same floating point numbers. However, there are some operations that can desynchronize these areas. For example, if you want to display more than 8 floating point numbers, you can use DROP:* operation that drops all registers in the FPU stack, but don't touch the parameters. Of course, this point also imply that after a DROP:* operation you can NOT perform arithmetic operations on the numbers that were entered before the DROP:*.

The DROP:* operation is implemented via a FPU INIT instruction. There are a couple operations that also execute a FPU INIT, like <* (Drop All) and FMT} (Format End).

As a counterpart, REMOVE (Remove parameters) is a special operation that removes all printf.exe parameters in the same way as <* (Drop All), but preserving the FPU stack. In a similar way, FMT{ (Format Start) allows to enter new printf.exe parameters preserving the FPU stack. Note that in printf.exe parameters can be a mix of integer and floating point numbers (and characters and strings), whereas the FPU stack can only keep floating point numbers.

As said before, all floating point operations are evaluated in the FPU stack. The fact that each result must be duplicated in the printf.exe parameters makes these operations somewhat inefficient. If these operations could be managed just in the FPU stack they not only would be faster, but certain special advanced operations that are too much complex to be managed in the printf.exe parameters could be implemented in a simpler way. Besides, the FPU allows that the four basic arithmetic operations be performed over anyone of the 8 FPU stack registers, not just over the last one. This point makes possible to perform advanced calculations in a simpler way. A good example of this point is the Mandelbrot Set program described later. Therefore, in this section some operations that just use the FPU stack, without reflecting their results in the printf.exe parameters, are described.

The method to make good use of these operations consists in: enter numbers, operate they in the FPU stack and get a result, store result in a storage register, clear printf.exe parameters (or use FMT{ operation), and recall the result from the storage register in order to print it. The LOAD (Load parameters) special operation is an easier way to transfer values from the FPU stack into the printf.exe parameters.

There are a limited number of operations that work this way, just in the FPU stack. These operations are modified forms of STO, RCL and stack management operations with a colon added at end, plus a couple new operations. The table below summarizes all these special "FPU only" operations.

Floating Point Unit Stack-Only Operations
  Standard
Operation
FPU Only
Operation
Name Operation Description
S
T
O
R
E
STOn STO:n Store FpuN = X Store X in FPU register N
STO[n STO:[n Store-Xchg FpuN <=> X Exchange X and FPU register N
STO+n STO:+n Store-Add FpuN = FpuN+X Add X to FPU register N
STO++n STO:++n Store-Inc FpuN = FpuN+1 Increment FPU register N
STO-n STO:-n Store-Sub FpuN = FpuN-X Subtract X from FPU register N
STO--n STO:--n Store-Dec FpuN = FpuN-1 Decrement FPU register N
STO*n STO:*n Store-Mul FpuN = FpuN*X Multiply FPU register N by X
STO/n STO:/n Store-Div FpuN = FpuN/X Divide FPU register N by X
   
R
E
C
A
L
L
RCLn RCL:n Recall Push FpuN Recall X from FPU register N
RCL+n RCL:+n Recall-Add X = X+FpuN Add to X the FPU register N
RCL++ RCL:++ Recall-Inc X = X+1 Increment FPU register X
RCL-n RCL:-n Recall-Sub X = X-FpuN Subtract from X the FPU register N
RCL-- RCL:-- Recall-Dec X = X-1 Decrement FPU register X
RCL*n RCL:*n Recall-Mul X = X*FpuN Multiply X by FPU register N
RCL/n RCL:/n Recall-Div X = X/FpuN Divide X by FPU register N
   
S
T
A
C
K
DUPn DUP:n Duplicate Push FpuN Duplicate FPU register N
XCHGn XCHG:n Exchange X <=> FpuN Exchange X and FPU register N
DROPn DROP:n Drop Drop FpuN Drop (eliminate) FPU register N
DROP* DROP:* Drop All FPU INIT Eliminate all FPU registers
RDNn RDN:n Roll Down   Rotate X towards FPU register N
RUPn RUP:n Roll Up   Rotate FPU register N towards X
   
P
A
R
A
M
S
FMT} FMT}: Format End   Remove printf.exe parameters
from previous FMT{ mark on
preserving FPU stack registers
<* REMOVE Remove params   Remove all printf.exe parameters
preserving FPU stack registers
DROP:* Drop registers Drop All Drop all FPU stack registers
preserving printf.exe parameters
  LOADn
 
LOAD:n
Load params   Load into printf.exe parameters
the value of FPU stack registers,
and drop out they from FPU stack

The way to identify the FPU register that will be operated on is the same used in stack management operations described before: via a position digit that is 1 for X, 2 for Y, etc. up to 8 for D as shown in the FPU stack registers table shown above.

Note that, in certain cases, there are several different operations that get the same result. This allows that you can select the operation that best describe your intentions, so you can write clearer code.

Some FPU-only STO operations over X register produce non useful results, although they are valid. For example: STO:1 is X=X, STO:[1 is X<=>X, STO:-1 is X-X=Zero, STO:/1 is X/X=One; the same happen with RCL operations excepting RCL:1 that works the same as DUP:1.

LOADn load in printf.exe parameters the FPU register N. LOAD* load all numbers entered in the FPU stack. If a colon is added (LOAD:n/LOAD:*), then the loaded FPU register(s) are also dropped out from the FPU stack.

The standard STOi and RCLi indirect addressing operations can also be performed as FPU only ones via a negative index that specify the FPU register, that is, -1 for X, -2 for Y, etc up to -8 for D. In this way, when using i Index register for indirect addressing, a value positive or zero in the I register specify a memory storage register, whereas a negative value specify a FPU stack only register.

An example of this feature is the program that compiles the statistics values described before modified in order to keep the statistics values in FPU registers instead of memory storage registers this way:

Register Value Description
R2 = B n Number of data points accumulated.
R3 = A ∑x Summation of x-values.
R4 = T ∑x^2 Summation of squares of x-values.
R5 = Z ∑y Summation of y-values.
R6 = Y ∑y^2 Summation of squares of y-values.
R7 = X ∑xy Summation of products of x- and y-values.

This is the segment that compiles the statistics values, that was modified in order to use the specified FPU stack registers:

   /* Part 0: Enter  B=0  A=0  T=0  Z=0  Y=0  X=0		*/ ^
   ZERO ZERO ZERO ZERO ZERO ZERO REMOVE          /* 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	*/ ^

This is a fragment of the code that calculate some of the statistical values:

   /* 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 that you must load a value in both places (FPU registers and printf.exe parameters) when you need to use any standard operation, like SQRT, that is the purpose of the RCL8 and RCL9 standard operations in last formula above. Note also that the value in the parameters is not modified by the FPU-only operations nor used as data by the standard operations, but it is important because it "reserves the place" needed to correctly evaluate the standard operation.

The complete program is in printf Example 1.5 - Two var statistics - FPU only.bat file.

A more extensive example of this feature is the Mandelbrot Set program described later.

String Operations

Strings are managed in printf.exe program in two separate parts: the data comprised of the actual characters, and its address that is the part used by the CRT printf function to show the string via %s format specification. When a "string" is entered, their characters are stored in an area reserved for they with a binary zero byte appended to the end (that marks the end of the string), and the value that appears in the printf.exe parameters is the address of (pointer to) the first character. An interesting detail is that this address is a 32-bits integer number, so it can be "managed" with the standard integer operators, like Dup, Exchange, etc. Some of these integer operators give useful results when they are applied to string addresses. A few examples will help to understand this point; it is suggested to copy these examples and execute they in the cmd.exe window.

printf "First: %s,  Second: %s\n" "One" "Two"
printf /" "Second: %s,  First: %s\n" "One" "Two" <>  /* Exchange strings */

printf /" "A string: \"%s\", *the same* string: \"%s\"\n" "Just one string" >  /* Duplicate string */

In the first example two strings are entered as explained before: their characters are stored in the data area and their addresses are entered as parameters of printf.exe (as any other parameter, like a number). In the second example the strings are stored in the same way, and the <> operation exchange the addresses of the strings (not their characters). In this way, the first address points to the second string and vice versa.

In the third example the > operation duplicate the address of the only string given (not their characters), so the same characters are displayed twice.

Besides integer operators, that may produce some useful results when they are used on strings, there are several functions specifically designed to work on strings. All these functions does not modify the data (characters) of the strings given in the parameters; they just may delete the addresses of the processed strings from printf.exe parameters. This means that if you store such strings addresses, you can still use they later.

For example, join function catenate the given strings: printf "Result: \"%s\"\n" "One" "Two" "Three" 3 join show: Result: "One Two Three". If you store the address of the 3 used strings, you can also show they:

printf "String \"%s\" is the join of \"%s\"+\"%s\"+\"%s\"\n" "One" ]1 "Two" ]2 "Three" ]3 3 join [1 [2 [3

Show: String "One Two Three" is the join of "One"+"Two"+"Three"

In this way, the integer storage registers can also function as string storage registers, although you should keep in mind that only addresses are stored here; the characters are stored elsewhere.

In despite of this, you must note that <* (Drop All, release strings) operation do liberate the data area of all deleted strings, so such an area could be used by any new string entered after. If you want to keep the strings, use DROP* (Drop All, preserve strings) instead that do not release the data of the strings.

String Functions
Name Description
Canonical form (CRT)
Example
Before --> func --> After
case Convert letters to lowcase/upcase in a string
case0(string)
case1(string)
"Some Letters" case0
case1
"some letters"
"SOME LETTERS"
atoi Convert a string to one/all integer numbers
atoi0(string)
atoi1(string)
"123"
"12 34 56"
atoi0
atoi1
123
12 34 56 3
atof Convert a string to one/all floating numbers
atof0(string)
atof1(string)
"47.250"
"12 34 56 78"
atof0
atof1
47.25
12. 34. 56. 78. 4
len Number of characters in a string
len(string)
"Nineteen characters" len "Nineteen characters" 19
getc Get one/all character(s) from a string
getc0(string,pos)
getc1(string)
"ABCDEFG" 3
"ABCDEFG"
getc0
getc1
"ABCDEFG" 3 'D'
'A' 'B' 'C' 'D' 'E' 'F' 'G' 7
putc Put one/all character(s) in a string
putc0(string,pos,'C')
putc1('1','2',...,'n',N)
"ABCDEFG" 3 'x'
'A' 'B' 'C' 'D' 'E' 'F' 'G' 7
putc0
putc1
"ABCxEFG" 3
"ABCDEFG"
xchc Exchange a character from/in a string
xchc(string,pos,'C')
"ABCDEFGHIJ" 3 'x' xchc "ABCxEFGHIJ" 3 'D'
movc Move one character (pointer)
movc0(pointer)
movc1(pointer,'C')
movc2(pointer1,pointer2)
"ABCDEFG"
"ABCDEFG" 'x'
"ABCDEFG" "01234567"
movc0
movc1
movc2
"ABCDEFG" 'A'
"xBCDEFG"
"0BCDEFG" "0123456"
cmpc Compare two chars (pointer), returns: -1 0 1
cmpc0(pointer)
cmpc1(pointer,'C')
cmpc2(pointer1,pointer2)
"ABCDEFG" 3 +
"ABCDEFG" 'x'
"ABCDEFG" "01234567"
cmpc0
cmpc1
cmpc2
"DEFG" 3
"ABCDEFG" 'x' -1
"ABCDEFG" "0123456" 1
dupc Duplicate a character several times
and create a string
dupc(char,N)
'#' 8 dupc "########"
dups Duplicate a string several times
dups0(string,N)
dups1(string,N,separator)
"Hello." 3
"Hello." 3 " + "
dups0
dups1
"Hello." "Hello.Hello.Hello."
"Hello." "Hello. + Hello. + Hello."
revc Reverse the characters in a string
revc(string)
"This is a test" revc "tset a si sihT"
revs Reverse the position of several strings
revs(str1,str2,...,strN,N)
"This" "is" "a" "test" 4 revs "test" "a" "is" "This" 4
gets Get part of a string (substring)
gets0(string,start,len)
gets1(string,start,end)
"ABCDEFGHIJ" 3 4 gets0
gets1
"ABCDEFGHIJ" "DEFG"
"ABCDEFGHIJ" "DE"
index Get indices of substring into a string
index(string,subs)
"This is a test" "a"
"This is a test" "is"
"This is a test" "x"
index "This is a test" 8 1
"This is a test" 2 5 2
"This is a test" 0
split Split a string in several substrings
split0(string)
split1(string,delims)
"This is a test"
"This_is_a_test"
"This_is_a_test" "_"
split0
split0
split1
"This" "is" "a" "test" 4
"This_is_a_test" 1
"This" "is" "a" "test" 4
join Join several strings in a longer string
join0(str1,str2,...,strN,N)
join1(str1,str2,...,strN,N,separator)
"This" "is" "a" "test" 4
"This" "is" "a" "test" 4 "><"
join0
join1
"This is a test"
"This><is><a><test"
shift Shift the position of several strings/numbers
shift0(str1,str2,...,strN,N)
shift1(i1,i2,...,iN,N)
shift2(fp1,fp2,...,fpN,N)
"This" "is" "a" "test" 4
10 20 30 40 4
10. 20. 30. 40. 4
shift0
shift1
shift2
"is" "a" "test" 3 "This"
20 30 40 3 10
20. 30. 40. 3 10.
repl Replace parts of a string
repl(string,oldStr,newStr)
"This is a test" "is" "at"
"This is a test" "is" ""
"Letters" "" "_"
repl "That at a test"
"Th a test"
"L_e_t_t_e_r_s"
cmps Compare two strings and returns: -1 0 1
cmps0(str1,str2)
cmps1(str1,str2)
cmps2(str1,str2,N)
cmps3(str1,str2,N)
"THE" "The"
 
"THE" "There" 3
cmps0
cmps1
cmps2
cmps3
"THE" "The" -1
"THE" "The" 0
"THE" "There" 3 -1
"THE" "There" 3 0

The case conversion of letters function is specifically designed for the characters of cmd.exe's Code Page 850. If a different code page is set, this conversion will not work as intended. You can review or modify the conversion table via the procedure described in Appendix #2.

Both atoi1 and atof1 function variants extracts all numbers that appears in the string that could be mixed with any other characters; the only requisite for a number to be converted is that it be preceded by a space, TAB or comma. The last generated value is an integer that indicate how many numbers were converted. Remember that in printf.exe you can enter a maximum of 8 floating point numbers.

In gets function if start is negative it specifies a backwards position from string end, and if len is negative it specifies a backwards position (not a lenght) from string end; if len is zero, no characters are get. In gets1 variant the end parameter is the position of the last character, not including it; if end is zero, up to the last character is get.

In repl function if the newStr is empty the matching oldStr is deleted, and if the oldStr starts or ends in asterisk the replacement part is modified. When oldStr starts in asterisk like this: string "*subs" "new" repl, the function replace from beginning of the string up to the first appearance of "subs". If oldStr ends in asterisk like this: string "subs*" "new" repl replace from the last appearance of "subs" up to the end of the string. If both asterisks are included like this: string "*subs*" "new" repl the function replace both parts at begin and end of the string and preserve the middle part. If oldStr is empty the newStr is inserted between every character of the original string.

In the split0 variant, the string is split at each space or TAB, but the parts of the string that are enclosed in quotes stay the same. In the split1 variant you can define the delimiter characters. In any case, several successive delimiters are treated as one.

The operation of index and repl functions is case-sensitive: the case of the substring must match the case of the base string. There is not an ignore-case variant of these functions in this version of printf.exe.

cmps function gets the "ordinal relation" of the strings and returns -1, 0 or 1 if the first string is less than, equal to or greater than the second one, respectively. The cmps0 variant is case-sensitive and the cmps1 ignore the case; if the strings have different lenghts, these two functions marks they as different. If shorter string is equal to first characters of longer string, the shorter string will be less than the other. cmps2 and cmps3 allows to compare just a given number of case-sensitive and case-insensitive characters, respectively.

The movc and cmpc functions with "pointer" type parameters are described below.

Character Pointers

As said before, the characters and the address of a "string" are stored in separate parts, and the part that is managed in the printf.exe parameters is the address. This "address" could also be called "pointer" because both terms refers (points) to a given character. The difference is subtle: in this context the "address of a string" refers to the beginning of the string, whereas a "pointer to a string" refers to any character in the string. How an "address" could point to a character that is not the first one? Simple: just add to it a number (displacement or offset). Remember that a string address IS a 32-bits integer number!

printf /" "The string is \"%s\" and is stored at address %i\n"  "Any string" >  /* Duplicate string address */

printf "Move pointer to a posterior character: %s\n" "ABCDEFGHIJ" 3 +   /* Show: DEFGHIJ */

movc is a multi-purpose pointer function that achieve the task of getc and putc standard functions.

The standard getc0 function requires the string (starting) address and a "character position" (offset) in order to get a given character; the position starts at zero and it is incremented up to the string length in order to process the rest of characters. The movc0 function directly uses a "character pointer". This pointer starts at the same address of the string, so it is never equal to zero, and it is incremented up to the address of the last character to process the rest of characters. The pointer version does not require the initial string address, so its use is simpler and faster. The same point apply when comparing standard putc0 vs pointer movc1 that puts (replaces) a character into a string.

movc2 is an advanced function variant that achieve the task of both getc0 and putc0 at once. It directly moves the character pointed by second pointer to the place of first pointer without enter/drop the character in the stack.

Moreover, the three variants of movc function can optionally increment their pointers after the move operation. To do that, just insert a "+" sign before the last digit to increment the source pointer, like in movc+0, or insert a "+" sign after the last digit to increment the destination pointer, like in movc1+. In the last variant you can insert anyone of "plus" signs, or even insert both signs to increment both pointers in the same operation like in movc+2+. This feature aids to write compact and efficient loops that processes several characters. Some comparative examples of these methods will be described later.

cmpc is a pointer function that allows to compare characters in a string in a simpler and more efficient way. Its operation and the possibility of increment their pointers are entirely similar to that of movc function.

cmpc1 variant compares the character in the string pointed by the pointer vs the character in the stack. cmpc2 variant directly compares the characters in two strings. In both cases the result is a -1, 0 or 1 that is loaded in the stack when the first character is less, equal or greater than the second, respectively.

cmpc0 variant get the offset that the pointer has with respect to the beginning of the string, that is, it gets the index or position of the character pointed by the pointer.

NOTE: when cmpc0/1/2 functions are used as a Test? (described later), then they does not return a value in the stack: the result of the comparison (if the characters are equal) is reflected in the Test? itself. In this case the cmpc0? variant will be True when the pointer points to any character in the string and will be False if it points to the end of string (byte with zero delimiter). This point applies exactly the same to the variants of the cmps function: when this function is used as a Test? the result will not be loaded on the stack; if the strings are equal, the Test? will be True.
Top

Block Programming

Version 2 of printf.exe application also offers the possibility of achieve basic programming. This feature not only provides the means to solve a wide range of numeric and text-processing problems, it also allows to take a first contact with computer programming in a very simple way. The programming scheme used in printf.exe is not the traditional one of common high-level programming languages, it is a much simpler one that still offers the same advantages of modern structured languages. This programming paradigm was adapted from Regular Expression Compiler (REC), a structured programming language derived from Lisp and developed about 1966 by Harold V. McIntosh that is based on just four control elements and a couple simple rules about Test evaluation. I nicknamed Block Programming this technology.

In it's simplest form, a program is a series of operations that are executed in order. That is it. From this point of view, all examples of printf.exe application we have seen so far are programs. For example, in order to get the result of algebraic expression (4+5)/(6+7) we use:

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

Previous RPN expression is really a program that means:

  1. Enter number 4.
  2. Enter number 5.
  3. ADD (4 + 5)
  4. Enter number 6.
  5. Enter number 7.
  6. ADD (6 + 7)
  7. DIV (4+5)/(6+7)
  8. Out (printf) the result

In other words: to get the result of algebraic expression (4+5)/(6+7) we need to execute previous 8 steps in order. Lets's call instructions the steps or operations that comprise a program, so each program is formed of a series of instructions. Note that if you alter the order of anyone of 8 previous instructions, you will not solve the stated problem (this would be a programming error). On the other hand, previous program is not the only way to solve this problem. We could think of a different program that solve the same problem. For example:

printf "Result: %f\n"  6. 7. ADD  4. 5. ADD  XCHG  DIV
  1. Enter number 6.
  2. Enter number 7.
  3. ADD (6 + 7)
  4. Enter number 4.
  5. Enter number 5.
  6. ADD (4 + 5)
  7. eXCHanGe positions of (6+7) and (4+5)
  8. DIV (4+5)/(6+7)
  9. Out (printf) the result

If there are several ways to write a program, which one should we use? This depends on a series of factors and the resulting programs may have different features. One program could be faster than another, but perhaps it is convoluted and difficult to understand. Another program could be clearer and good example for educative purposes, but it is slower. We will see examples of this point later. Could you think of a program different than previous two that solve the same problem?

Let's review a different example. The algebraic formula to calculate the area of a triangle is: area = (base * height) / 2. If we would need to calculate the area of a triangle with base=8 and height=12, we could use this "program":

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

After that, if we need to calculate the area of another triangle, this time with base=33.5 and height=18, we do this:

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

That is, the operations to solve our new problem are the same as before; we just need to change the initial data. This means that these operations: MUL 2. DIV represents a general-use program that can solve the area of any triangle! Isn't it? ;)

However, how we can convert these instructions into a real program that can run in an independent way? That is, that don't requires to hard-write the values of the changing data? In other words, that be capable of get, or read, or input (whichever term you prefer) the values of the data by itself. Well, in order to do that we need a new class of instruction: an operation that allows to enter data that was not originally in the RPN expression, but that will be inserted in a certain place when the program run.

In the same way, when a program executes it frequently needs to show different messages at different points. However, the standard printf.exe operation is to show one output specified by one format when the program ends. We need a method to show several outputs with different formats at any point we wish.

This is the purpose of the Input/Output operations.

Input/Output Operations

Input/Output Operations
Oper Name Description
GETK Get Key Get one key (character) from keyboard
IN Input Line Read a line (string) from keyboard
CURS Cursor Get or Set cursor position, visibility and size
OUT Output Show current data with current format
FMT{ Format Start Allows to enter a new format, data and color
FMT} Format End Removes new format and data from previous FMT{ mark on

CURS (Cursor) operation controls the cursor in this way:

Cursor Function Operations
Oper Name Description
CURS Cursor Get Get cursor position in Y (line) and X (column) registers
CURS0 Cursor Hide Turn off the cursor
CURS1 Cursor Show Turn on the cursor
CURS2 Cursor Set Set cursor position from R0 (column) and R1 (line)
CURS3..9 Cursor Size Set cursor size from CURS3 (minimum) up to CURS9 (maximum)

OUT (Output) operation show the current data with the current format. For example:

printf /" "The number is: %i\n" 10 OUT  < 20 OUT

Show:

The number is: 10
The number is: 20

Note that if you use OUT operation, the "automatic output" of the final printf.exe result at end is canceled (this also happen if you use a program, more about this point later).

FMT{ (Format Start) delimiter allows to enter a new format and new data after it that will be used in the next OUT operation. For example:

printf "A message\n" OUT  FMT{ "Two numbers: %i %i\n" 10 20 OUT  FMT{ "A string: %s\n" "Hello" OUT

Show:

A message
Two numbers: 10 20
A string: Hello

FMT} (Format End) operation removes all data entered after the previous FMT{ delimiter, including the delimiter itself. For example:

printf /" "A message\n" OUT  FMT{ "Two numbers: %i %i\n" 10 20 OUT  FMT{ "A string: %s\n" "Hello" OUT  FMT} <> OUT  FMT} OUT

Show:

A message
Two numbers: 10 20
A string: Hello
Two numbers: 20 10
A message

The FMT{:r delimiter also allows to define the color used to show the text in the corresponding OUT operation via an attribute stored in an integer storage register. For example:

set /A "GREEN=10, RED=12"
printf /" "Message\n" OUT  GREEN atoi ]4 RED atoi ]6  FMT{:4 "Green numbers: %i %i\n" 10 20 OUT  FMT{:6 "Red string: %s\n" "Hello" OUT  FMT} <> OUT  FMT} OUT

Show:

Message
Green numbers: 10 20
Red string: Hello
Green numbers: 20 10
Message

Color attributes can also be defined in the RPN code for a simpler use; an example appears in the printf Example B - Color Attributes.bat file that uses the same HTML (Web pages) standard color names.

The color defined in the storage register is dynamic, that is to say, such a value will be retrieved every time that new text will be displayed on the screen. If that value changes, the new color will appear in the next OUT operation.

Note that the color of the last OUT operation will be used in the new text that appear on the screen after printf.exe program terminate. This feature can be used in .BATch files to show text with different colors. If you want not this effect, execute an OUT operation with an empty format before the program ends.

Remember that <* (Drop All) operation removes all data entered after the current FMT{ format string (or after the initial format string).

IMPORTANT: After FMT} operation you can NOT perform arithmetic operations on the floating point numbers entered before, that is, in a previous FMT{ FMT} level. This happens because FMT} execute a FPU INIT instruction. If you want to preserve a floating point number in order to use it after a FMT}, you must keep it in a storage register and recall it after the FMT}. Note also that FMT{ operation allows to enter a new set of printf.exe parameters but does not clear the FPU stack, so both areas could be desynchronized if you enter floating point numbers. This point was described with detail above. If you want not to make good use of such a feature, it is suggested to <* (Drop All) or DROP:* (Drop FPU registers) before use FMT{.


Input operations allows to enter data into a RPN expression. When an input operation is executed, the program waits to read data from the keyboard so at that point the user must provide such a data. When the data is completed, it is entered in the RPN expression at the place of the input operation and the program continue with the next instruction.

The simplest input operation is GETK (Get Key). It gets one key from keyboard and returns its value as a character (integer). For example:

printf /" "Press a key: " OUT  FMT{ "\nThe key pressed is %i ('%c')\n" GETK > OUT

GetKey can read any key. Character keys returns its ASCII code, special keys returns a negative value. You can consult the values of special keyboard keys in printf - GetKey codes.txt file. These values are based on the position of the keys in the original IBM-PC extended keyboard. You can review ShowKeyCodes.bat file to understand how these values were generated.

IN (Input Line) operation read a line from keyboard and enter it as a string followed by its length. Before IN operation you must enter the maximum number of characters that can be read; this number is eliminated by the input operation. For example:

printf "Enter a string: " OUT  FMT{ "\"%s\" have %i characters\n" 80 IN  OUT

IN operation returns an empty string and 0 lenght if the line read is empty. However, if the EndOfFile of a (redirected) input file is reached, IN returns just a -1 with no string before.

These operations allow us to write an independent program that can request the needed data by itself. For example, if we go back to our "Area of triangle" problem, we could solve it in this way:

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

Note that numbers entered via keyboard in this way are left behind FMT{ marks, so those numbers must be moved to a place after the last FMT{ mark in order to be included in the final calculation. The way to solve this point in this example is via storage registers. However, there are other methods to solve this problem that will be discussed later.

So far so good... Now we have a method to write a program that can get its data and solve a problem, so now what? This is not that impressive. What would really be important would be solving hundreds or thousands of problems of the same type using the same method, or solving problems that may have different answers given by different formulas.

In order to do that we need to repeat sections of a program, or to conditionally execute parts of a program, etc. This is what is really called programming.

As stated before, the method used in printf.exe application to write programs is based on just four control elements (Begin, Repeat, Quit and End) and a few concepts that were taken from the REC programming language, like Code block, Control flow transfer and Conditional tests. The scheme below show ALL operative rules of block programming. In the next sections below these rules will be explained in detail.

Code Block

A code block is a series of operations enclosed between ( (left parentheses) that we could name BEGIN, and ) (right parentheses) we could name END. We call delimiters these two characters. For example:

printf "First integer: %i,  second integer: %i\n"  10 ( 20 )

In this example there is one code block that contains the number 20. When the RPN expression is evaluated, the operations inside the code block are executed from left to right, in the usual way; however, in this example the output is not displayed. When a RPN expression contains a code block, the C printf function is not automatically invoked at end of the expression, so we need to explicitly show the output via OUT instruction. For example:

printf "First integer: %i, second integer: %i\n"  10 ( 20 ) OUT

This example show First integer: 10, second integer: 20 as usual.

Code blocks can be nested one in each other. For example:

printf "An Integer: %i.  A String: %s.  A Float: %f\n"  ( 25  ( "Hello, world" ) 44.33 ) OUT

In this example there are two code blocks. The first one contains three elements: the integer 25, a nested code block, and the floating point 44.33. The second code block just contain a string. When block programming is used, all operations belongs to or are placed in a certain code block, and just in one code block. In this example the string belongs to the nested code block, precisely. The first code block does not contain any string; it contains two numbers and a nested code block.

In this way, this phrase: this operation works on its code block indicate that such an operation does not process any other block that can be nested inside the original block (where such an operation is placed). For example, if we would have two new operations called A and B and we would utilize they this way:

printf "An Integer: %i.  A String: %s.  A Float: %f\n"  ( 25 A  ( "Hello, world" B ) 44.33 ) OUT

... then operation A would work over first block only and operation B on nested block only, that is, operation A can not modify (nor reach to) the string contained in the nested block.

Control Flow Transfer

The execution of the RPN elements typically happens from left to rigth, starting at the first element and ending after the last element. This execution path is called control flow. The particular operation that is executing at any given time is said to "have the control".

We could alter the standard execution path of a program via a control flow transfer instruction that cause that the execution path jumps to another point in the RPN expression. In fact, this is the purpose of BEGIN and END delimiters: mark the points where the control flow can be transfered.

There are two control flow transfer instructions: : (colon) called REPEAT and ; (semicolon) called QUIT. REPEAT instruction transfer the control flow back to the beginning of its code block. For example:

printf "Hello, world\n" ( OUT : )

This example show "Hello, world" multiple times in an endless loop until the program is cancelled via Ctrl-C key.

QUIT instruction transfer the control flow forward after the end of its code block:

printf "%s %s\n"  ( "This appears on the screen" ; "This don't appears" ) "The end" OUT

You can visually observe these control flow transfers in the scheme seen above.

Test/Condition

A test is an operation that, when it is evaluated, it answers if a condition is True or False. If the condition is True, the control flow continue to the next operation (this is the old "Do-If-True" rule used in HP calculators). If the condition is False, the control flow is transfered forward until pass the next control flow transfer instruction (REPEAT or QUIT) placed in the same block. This simple scheme allows to assemble the basic building blocks that all computer programs are comprised of.

All tests in printf.exe block programming are written with a question-mark character at end. The simplest conditional tests are a few integer RPN operators with a question mark character added. The condition stated in these operator/test combination is: Is the result not zero? For example, the -- (Decrement) operator subtract 1 from the last integer number. When the question mark is added this way: --?, the decrement operator becomes a Test? that is True as long as the decrement result is not zero. A simple example:

printf "Turn number %i\n" 10 ( OUT --? : )

At beginning of this example a number 10 is entered, the OUT instruction is executed and the message Turn number 10 appears in the screen. Then the --? operator is executed, so the 10 becomes 9. Because the result is not zero, the control flow continue. The REPEAT : instruction is executed, and the control flow is trasfered back to the beginning of the code block.

The OUT instruction is executed again and now show Turn number 9. The process is repeated and in the next cycle Turn number 8 appears in the screen. The loop continue in the same way until Turn number 1 is displayed. After that, the result of the decrement operation is zero, so the --? test is False. Then the control flow is transfered forward until pass the REPEAT instruction, so the code block's end is reached and the whole program ends (this behavior is similar to "Decrement and Skip on Zero" (DSZ) instruction of HP's RPN calculators). This way to execute things in a repetitive loop is called Do-While: Do show the number and decrement it While the result is not zero. There is another construct called While-Do in which the Test? is evaluated first, so it is possible that the "Do" actions would not be executed even once.

Another example: the & (Bitwise AND) operator allows to test for individual bits of an integer number. In the internal (binary) representation of integers the least significant bit (that corresponds to the number 1) is set if the number is odd and clear if the number is even. In this way, if we operate any number and 1 with the &? op/test combo, the result is the answer to the question: "Is the number odd?". For example:

printf "Number %i is %s\n" 10  /"  > 1 ( &? < "Odd" ; < "Even" ) OUT

This example start with a number. We first duplicate > the number and enter a 1. Then, the &? do a Bitwise AND and get just the last bit of the number. If this bit is set (the result is not zero): the result is < dropped, the "Odd" string is entered and the QUIT ; control flow instruction is executed, so the control is transfered forward after the end of the block. Otherwise the control flow is transfered forward until pass the QUIT ; instruction, so the result is < dropped and the "Even" string is entered. After that a line is displayed, like Number 10 is Even or Number 11 is Odd. This way to conditionally execute one of two possible paths is called If-Then-Else.

Note that if a test is False and there is not any REPEAT/QUIT instruction ahead, the control flow exit from the block without reach the ) block's END delimiter.

On the other hand, if a test is True and there is not any REPEAT/QUIT instruction ahead, the control flow eventually reaches the ) block's END delimiter.

These two different ways to exit from a block will be important later, when we describe the last operational rule on printf.exe block programming.

Basic Conditional Tests

A Conditional test is the key to assemble loop cycles (While-Do, Do-While, For-Next) and conditional execution (If-Then-Else, Case/Switch) constructs via the "block programming" scheme that allows to write useful programs. In this way, a rich set of conditional tests aids programmers to write simple and efficient programs.

All conditional tests used in printf.exe block programming ends in question mark character. The simplest tests are a few integer operations with a question mark added at the end that works as an "operator and test combo": once the operation completes, the test part checks if the result is not zero.

Basic Conditional Tests
Operation Perform
? Test integer
]n? Test storage register N
--? Decrement
]--n? Decrement storage register N
!? Boolean NOT
&? Bitwise AND
|? Bitwise OR
%? Division remainder
getc? Get char from string
movc? Move a character
cmpc? Compare two characters
cmps? Compare two strings
CD? Change directory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


These tests are True if the result is not zero. The new ? (Test integer) and ]n? (Test storage register) operators test if the value of the last integer number or the specified storage register is not zero, respectively, so they allows to check the result of any other operation.

With --? or ]--n? operators is easy to repeat a loop a certain number of times. &? allows to test for individual bits in a number. %? test if a number is multiple of another one.

Both getc? and movc? allows to easily process all characters in a string.

Functions cmpc? and cmps? compare characters or strings, but if they are used as a Test? they will not load the result in the stack: the Test? will be True if the characters or strings are equal.

This point also apply to CD? function: if the directory was successfully changed the Test? will be True; no value is loaded in the stack. CD function is described later.

NOTE: You can use indirect addressing in Test storage register and Decrement storage register operations in the usual way, like in ]i? or ]--i?. However, you can not use the post-increment/decrement in these tests.

To process the characters of a string insert the zero-based index of the desired character and execute getc?: the character will be loaded after the index. If this character is the zero delimiter inserted at end of the string, the Test? is False. For example, the next code counts the number of characters in the string, so it is equivalent to len predefined function:

printf /" "\"%s\" have %i characters\n"  "Any String"  0 ( getc? < ++ : < ) OUT

This example show the base method to process characters in a string. This method can be modified in order to achieve other similar tasks; for example, convert characters to uppercase or lowercase letters, etc. You can review some of these methods in printf Example 2 - ProcString.bat file; be aware that the conversion methods uses the comparison tests that will be described in a section below.

Program in Didactic Form (Batch files)

Perhaps the most difficult aspect when you write a large program using the block programming scheme is to keep track of the printf.exe parameters (stack contents) after each operation. In order to facilitate this task, you can write programs in a different and clearer way we call Didactic Form. HP calculators used a "Programming Form" printed in a paper sheet for the same purpose in which the user could write the stack contents after each program's operation. The method that we will use is to divide the instructions of the printf.exe program into several lines so that each one includes a descriptive comment. However, this cannot be properly done on the cmd.exe command line; we need a place to store a long printf program so that it is easy to create and edit.

The usual way to do this is to create a Batch script file (text file with .bat extension). These types of files contain a series of commands that can be executed automatically, such as our printf.exe command that includes a long advanced RPN program. To do this, follow these steps:

  1. Create a text file (right-click the mouse into a folder area and select: New -> Text document) and change the extension from .txt to .bat when you give the file name; confirm you want to change the extension.
  2. Right-click the mouse over the new file and select: Edit; the file should open in the Windows Notepad editor.
  3. Insert the line @echo off at beginning of the file.
  4. After that, insert the printf command with the whole program.
  5. Change all percent % characters by double-percent: %%
  6. Change any individually ^< ^> ^| ^& escaped character by the use of Quoted switch, like /" < /", or /" > /", etc.
  7. Split the RPN expression in individual lines as you wish.
  8. After each line include a descriptive comment enclosed between /* and */ (Comment) delimiters.
  9. At end of each line insert a ^ (caret) character, except after the last one. Be careful to not insert any space after the caret.
  10. If you use the /" Quoted switch, close it in each individual line and open it again if a posterior line requires it.

You must also enclose in quotes any special character placed in /*comments*/. Note that you can combine an open /" Quoted switch with a single closing quote placed at end of the comment. See examples in the included *.bat files.

When the program is ready, close the file and save it. In order to execute the printf.exe program, enter the name of the Batch file in the cmd.exe window (like you previously did with the printf command itself). If the Batch file name include spaces, enclose the name between quotes to execute it.

For example, the last string length example shown above:

printf /" "\"%s\" have %i characters\n"  "Any String"  0 ( getc? < ++ : < ) OUT

... could be rewritten in this didactic form:

@echo off

printf "\"%%s\" have %%i characters\n" /* format */ ^
   "Any String"		/* "string"		*/  ^
   0			/* "string" 0		*/  ^
   (			/* WHILE getc?		*/  ^
      getc?		/*    "string" 0 C	*/  ^
      /" < /"		/*    "string" 0	*/  ^
      ++		/*    "string" 1 ,2,...	*/  ^
   :			/* REPEAT		*/  ^
			/*    "string" len 0	*/  ^
      /" < /"		/*    "string" len	*/  ^
   )			/* ENDWHILE		*/  ^
   OUT			/* show the result	*/

In this didactic form you can review the stack contents before each operation, so you can be sure that all operation parameters are correct. As an additional benefit, the control constructs (like While-Do, If-Then-Else, etc) are clearly marked and delimited. This form greatly increases the readability of block programming and brings it closer to high-level languages. You should use this form to write your own programs. This method allows programs up to 8190 characters long. The lines in this example are around 40 characters each; this means that you can write a similar program about 200 lines long.


The functions getc and putc operate based on a start address of a string plus an offset index. If these functions must operate on different strings, it can be a bit cumbersome to move the parameters of the stack to arrange them in the correct way to use each function. For example, the following program duplicates one string into another, thus doing the equivalent of the 1 dups function:

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		*/

On the other hand, the movc function uses a pointer that directly points to a certain character within a string, so its use is easier than getc/putc which require two quantities (the start address and the offset) to take the same character. For example:

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		*/

Furthermore, the movc2 variant can take a character from a string and store it directly in another string without using the stack, so its use it is even simpler and more efficient. Finally, the three variants of the movc function allow auto-incrementing the pointers used after the corresponding character has been moved, which allows writing shorter and faster programs. The reader is invited to review examples of this point in the printf Example 3 - Index vs Pointer.bat file.


The standard for running programs on the command line is to place after the command a series of parameters, which are values that the command takes to work with. In the printf.exe command the parameters consist of the operations of the RPN program. However, it is convenient to use a way to trasfer the parameters from the Batch file (which contains the printf.exe program) into the RPN data. This would allow the Batch/printf.exe file combination to be used in a standard way. Here it is:

@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			*/

The set "parameters=%*" command takes the parameters from the Batch file and stores them in the variable parameters. Putting the name of this variable in the RPN program will input its value as a character string; that is, the parameters of the Batch file are entered. The split function splits this string into individual parameters and the shift function allows to process them one by one. The didactic form of this program is in printf Example 4 - Parameters.bat file.

Note that a program in didactic form run slower than in standard form. You could develop and test the program in didactic form and convert it to standard form when it is ready. GetStandardForm.bat is an auxiliary Batch file that aids to do this conversion; you just need to check that the generated code is correct in a couple points: if there are several adjacent spaces in a string they are reduced to just one, and perhaps insert a closing /" Quoted switch before a "<string>" that contain special characters (and open it again after, if needed). Note that all program lines must contain a /*comment*/ at end in order to be properly converted by GetStandardForm.bat

Flags Tests

The Flags is a facility taken from HP calculators. They are variables that directly represent the True/False values. SFn operation Set Flag to True. CFn operation set flag to False (Clear Flag). Fn? test ask for the Flag value. The number n must be a digit in 0..9 range, so there are 10 Flags in this version of printf.exe. The Flags represent a simpler alternative to, for example, store a zero in an integer register in order to indicate False and change it to 1 to indicate True. Some examples of their use will be shown later.

Standard Numeric Tests

The Standard Numeric Tests in the table below compare the last number (that we call "X") versus zero (with integer and floating point versions), or versus the previous number called "Y", also with integer and floating point versions. There is also a new type of tests that compare "Y" versus "X".

Standard Numeric Tests
One-operand tests: X vs 0 Two-operands tests: X vs Y Reverse tests: Y vs X
Int Float Condition Int Float Condition Int Float Condition
>0? GTR0? X greater than 0 >y? GTRy? X greater than Y >x? GTRx? Y greater than X
>=0? GEQ0? X greater or equal 0 >=y? GEQy? X greater or equal Y >=x? GEQx? Y greater or equal X
<0? LSS0? X less than 0 <y? LSSy? X less than Y <x? LSSx? Y less than X
<=0? LEQ0? X less or equal 0 <=y? LEQy? X less or equal Y <=x? LEQx? Y less or equal X
==0? EQU0? X equal 0 ==y? EQUy? X equal Y ==x? EQUx? Y equal X
!=0? NEQ0? X not equal 0 !=y? NEQy? X not equal Y !=x? NEQx? Y not equal X

Historically, since 1968 when the HP-9100 (the first programable HP calculator) was introduced, the comparison tests included the "X vs 0" and "X vs Y" tests. However, the X vs Y tests presents an inconsistency problem when they are compared vs. the rest of two-operands operators.

The "natural" order to perform operations in RPN logic consists in enter the first number, enter the second number, and then execute the operator. All two-operands operators works operating "Y" vs "X" this way, that is: "Y-X", "Y/X", etc. However, the comparison tests works in reverse order comparing "X" vs "Y": "X>Y", "X<=Y", etc. This detail force us to alter the "natural" code in order to get the desired result. For example, if we want to know if the result that is in X register is greater than 20, we need to insert 20 x<=y? (using the "less or equal" opposite comparison) or 20 x<=>y x>y? (exchange the operands before the comparison). A simpler and clearer way would be to insert the 20 and just test if "y>x?"

In order to facilitate the use of comparison tests to beginners, printf.exe application also includes the reverse (natural) order comparisons that are identified in the table above as "Reverse Y vs X" tests. This table show the three types of printf.exe standard numeric comparison tests. The first two are an exact duplicate of HP calculator tests and should be used in the same way; for example, when you translate an original HP program to printf.exe application. The use of third type tests is encouraged to beginners because they are simpler to understand. In this document, and in the example programs included in this package, we will use now and then the clearer printf.exe's "Y vs X" comparisons.

NOTE: In printf.exe versions previous to 2.80, the short comparison forms (>? >=? <? <=? ==? !=?) are equivalent to the standard X vs Y new forms (>y? >=y? <y? <=y? ==y? !=y?). The same happen with the floating point short comparison forms (GTR? GEQ? LSS? LEQ? EQU? NEQ?) that are equivalent to the new standard X vs Y forms (GTRy? GEQy? LSSy? LEQy? EQUy? NEQy?). Although the old short forms will still be supported, it is suggested to use the new forms because they are clearer.

For example, to get the MAX of two integer numbers, use ( >y? <> ) <, that is, if the last number X is the maximum, exchange numbers so the lesser be the last. After that, drop the last number (that always will be the lesser) and keep the MAX.

In a similar way, to get the MIN of two integers: ( <y? <> ) <.

To get the MAX of two floating point numbers, use ( GTRy? XCHG ) DROP, and to get the MIN: ( LSSy? XCHG ) DROP.

You can also use =0?, =y? and =x? for "X equal 0", "X equal Y" and "Y equal X"; or <>0?, <>y? and <>x? for "X not equal 0", "X not equal Y" and "Y not equal X" integer tests.

We can write multiple examples of printf.exe operations in a simpler way if we make good use of other Batch cmd.exe commands. For example, to test all integer tests in a single line we can use FOR command:

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

The same tests for floating point numbers:

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

You can change the order of values or test a single value vs. zero in order to complete these tests with the rest of conditions.

We can now complete the conversion to uppercase letters that use >=x? and <=x? integer tests:

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

Remember that characters are managed as integers, so the integer tests are also character tests. The Didactic form of this conversion is in printf Example 2 - ProcString.bat file.

Conversions between uppercase and lowercase letters are based on the fact that these two set of letters are separated by 32 positions in the standard ASCII character table. However, this is not true in the case of extended (foreign, code beyond 127) characters. As reference, a table with some characters used in Spanish is shown at right side. The shown numbers are the positions of such a characters in the code page 850. Of course, if cmd.exe uses a different code page, these characters could appear in different positions or even not appear at all. Remember that you can convert all upcase/lowcase letters of the code page 850 via the case string function.

Another example of standard numeric tests is a multiplication table. This example include two nested code blocks:

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

The didactic form of this example is in printf Example 5 - Multiplication table.bat file and we reproduce it here so you can review it:

printf " %%3i"	/* format		*/ ^
   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		/*    show it		*/ ^
   FMT}		/*    clear EOL FMT	*/ ^
/" < /"		/*    i			*/ ^
   :		/* REPEAT		*/ ^
   )		/* ENDWHILE		*/

This is the last time we reproduce a complete didactic form here. There are several example programs in didactic form included in the printf.exe package that we will describe later. You can review these examples in your Windows Notepad text editor by opening the corresponding .bat file; to do this, click over the file with the right mouse button and select "Edit".


Another example derived from GETK operator consists in read a line from the keyboard, that is, simulate the IN function operation. The program will take keys one by one and check they: if the key is a standard ASCII character (greather or equal 32), it will be inserted in the result string and showed on the screen. If the key is BackSpace (ASCII 8 character: BS) the last entered character will be deleted from both the string and the screen. If the key is Enter (ASCII 13 = CR) the program terminate and returns the line read and its length (the same as IN). Here it is:

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

Before start, the maximum number of characters that can be read must be given (the same as IN) that in this example is 80, which is stored in register 2 (via ]2). First, a string with such a number of characters is created (via '$' <> dupc), its address is stored ]1 in register 1 and the index/counter of characters in the string is initialized to 0.

The process consists in a While repetitive loop ( that contains these parts: a key is read GETK and if it is the "Enter" key 13 ==?, the loop is canceled ;. Otherwise, the "Enter" is drop < and test the key vs "BackSpace" 8 ==?. IF is a BS (: delete both numbers 8 < < and left as "last number" the character index. Then, check if any character was entered ==0? and cancel the IF ; if not; else decrement -- the character index/counter and delete the last character from the screen (showing a BS+space+BS FMT{ "\b \b" OUT FMT}). The "IF is a BS" block ends here ) and go back : to the While loop. Block programming is very entertaining! Isn't it? ;).

In the last part the previous 8 is deleted < and if the key is less than 32 32 >? (a control character) both the 32 and the key are deleted < < and go back to the While loop :. Otherwise is a standard ASCII character, so the 32 is drop <, the character is stored in register 0 and eliminated ]0 <. Then, the maximum number of characters in the string is recalled [2 and, if the index reaches this limit ==?, the limit is drop < and go back to the While loop :; otherwise the limit is drop <, the character is output in the screen FMT{ "%%c" [0 OUT FMT}, the character is inserted in the string [0 putc, the index is incremented ++ and go back to the While loop : that ends at this point )

When the While loop breaks because an "Enter" key, a new line is shown FMT{ "\n" OUT FMT}, both numbers 13 are deleted < <, a 0 is entered as the string delimiter and it is stored in place putc. Finally, the result is displayed FMT{ "Line read: \"%%s\"\n" [1 OUT.

This basic read line method can be modified in order to get other similar tasks, with additional features. For example, in order to read a password just change the output character in FMT{ "%%c" [0 OUT FMT} by an asterisk: FMT{ "%%c" '*' OUT FMT}. You can also convert the character read to uppercase letters using the method seen before, or restrict the input to just digits by changing the restriction of "not less than 32" to "between 48 and 57" (ASCII codes of "0" and "9", respectively), et cetera. The didactic form of these programs is in printf Example 6 - ReadLine.bat file.

Advanced Input/Output Operations

The operations described in this section are the Test? forms of standard GETK (Get Key) and IN (Input line). There are also some advanced forms of IN and OUT operations that allows to read and write text files, and the new CMD and SYSTEM operations that execute a cmd.exe command. For completeness, the standard input/output operations described above are also included in the table below.

In order to process a text file you must first open it, that imply to get the file by its name and connect it to a number, called handle, that will be used in posterior operations over the file. If new data was written to the file, the handle must be closed (disconnected from the file) before the program ends. In the following functions N value identify the handle and must be a single digit in 0..9 range.

Advanced Input/Output Operations
Operation Function Description
Cursor Control CURS
CURS0
CURS1
CURS2
CURS3..9
Get cursor position in Y,X
Hide cursor
Show cursor
Set cursor position to R0,R1 (x,y)
Set cursor size from CURS3 upto CURS9
Standard output OUT Send formatted output to the screen via "format" data1 data2 ...
Format Start/End FMT{
FMT{:r
Start a new format and data for OUT, up to end it with FMT}
and set the color attribute for such a text from R integer register
Get a key GETK
GETK?
GETK?:r
Wait for a key press
No wait: if there is not a key press ready, is False
Wait for the next milliseconds time-slice given in R integer register
Standard input maxlen IN Read a line from keyboard
Execute command
and open a pipe
"command" CMD Execute cmd.exe's "command" and open a Stdin pipe to read its output
Input and Test? maxlen IN? Read a line; at EndOfFile close the last Stdin pipe open by CMD
Open input file "filename" IN{?:n
"filename" IN{+?:n
Open the file for input in handle N
Open the file for update in both input and output handles N
Input from handle maxlen IN?:n Read a line from handle N; at EndOfFile close the handle
Open output file "filename" OUT{:n
"filename" OUT{+:n
Create and open the file for output in handle N
Open the file for append (output at EndOfFile) in handle N
Output to handle OUT:n Send formatted output to handle N
Close handle OUT}:n
OUT}+:n
Close output handle N
Set file size to current FP position and close handle N
Move file pointer SEEK#:n:p Move FP position of handle N to position P from origin #
  Input: SEEK0=From start, SEEK1=From current, SEEK2=From end
Output: SEEK4=From start, SEEK5=From current, SEEK6=From end
Execute command "command" SYSTEM Execute cmd.exe's command; returns its %errorlevel%
Change directory "pathname" CD Change current directory; returns 0 for OK or -1 for Error.

GETK and IN input functions seen before can also work as tests if a question mark is added at end, as usual. IN? operation read a line from keyboard (or from a redirected input file) and leaves in the stack the string read and its length. When the End Of File is reached, a -1 is left in the stack and the Test? is False. In any case, the maxlen value is eliminated. For example:

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

This basic method of process input lines can be enriched with additional features. For example, to enumerate the lines:

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

Note that the use of <* (Drop All) operation in these examples allows to re-use the same data area to read all lines in a file; otherwise, the printf.exe string data area (with 10 KB of space) could be exceeded by file contents. The didactic form of these examples is in printf Example 7 - ReadFile.bat file.

GETK (Get Key) standard function waits until a key is pressed and then return its value. If you convert it to a test this way GETK?, then the operation returns immediately: if a key was pressed at that moment, GETK? returns it and the Test? is True; otherwise nothing is returned and the Test? is False. This behavior can be used to interrupt a cyclic process when a key is pressed. The last example above can be modified to pause the display when a key is pressed:

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

After a line is shown, the ( GETK? GETK ) part test if a key was pressed; if so, another key is read before continue with the output loop. Test this program with a very large file. This is a very simple method to insert a pause in any cyclic process.

Another interesting use of this feature is to control an animation. A program can show a figure in the screen that moves at regular intervals. When the program detects a key press via GETK? test, it could alter the figure movement. This is the basis to write animated games programs!

In order to facilitate this type of programs, GETK? function also allows to set a delay time interval. Every time that GETK?:n is executed it waits for the next clock time-slice of the number of milliseconds stored in the given integer storage register. This feature makes very easy to write a program that moves an animation at a given rate and to change such animation speed.

The next program is a very simple example of an animation whose speed can be controlled via Left-Arrow and Right-Arrow keys; the program ends when the Enter key is pressed:

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

The "\b %c" format return the cursor one character, erase the last character shown and show a new character, that is the ASCII 219 block character in 437 and 850 code pages; this method creates the illusion that the block moves from left to right.

The GETK?:1 test delay the execution the number of milliseconds stored in integer storage register #1, that was initialized to 100 milliseconds.

The Left-arrow (-75) key increments the delay by 10 milliseconds, so it makes the process slower. The Right-arrow (-77) key do the opposite thing. The Enter (13) key terminates the process.

When a key is detected, a -75 is entered to check if the key is a Left-arrow; if so, a 10 is entered to increment storage register 1 and then the 10 is dropped; anyway, after that the -75 is dropped. The same is done with -77 for the Right-arrow key. The Enter key does not insert any additional value, so just one drop is used after it. The last drop is to eliminate the key pressed itself.

Note that Enter key just clear the flag number 1 and that the whole process is repeated while the flag 1 is set. The use of a flag this way is a very simple method to break a cycle that don't enters any value in the stack.

The didactic form of this program is in printf Example 8 - Animation.bat file.


CMD is a powerful function designed to execute a cmd.exe command and process its output lines. To do that, CMD function asynchronously execute a spawned copy of the cmd.exe command processor and redirects its Stdout standard output stream into printf.exe Stdin input stream (pipe), so it can be read via IN? operation. For example:

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

An interesting feature of CMD function is that its operation can be nested several levels deep. When an active CMD is executing and IN? operation is reading its lines, you can start another CMD function and read its lines via nested IN? operations. When the EndOfFile of the second CMD is reached, the second pipe is closed and IN? operation reverts to continue reading lines from the first CMD function. We can make good use of such a feature in several different ways.

For example, we can write a program that process the names of all text files via "DIR /B *.TXT" CMD operation and then, for each file found, process the lines of such a file via a nested "TYPE filename" CMD operation. Here it is:

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 that the "TYPE \"" [1 "\"" 3 "" join1 segment in the middle is the one that assemble the TYPE "filename" command using the filename read from previous "DIR /B *.TXT" command. The didactic form of this program is in printf Example 9 - Type files.bat file.

When IN? read a (redirected) file, it returns a -1 in EndOfFile condition; however, when IN? is reading the piped output of a CMD function, then in EndOfFile it returns the ERRORLEVEL value of the terminating cmd.exe command.

If you want not to process the output of a cmd.exe's command, just execute it, use SYSTEM function instead. If the command parameter of SYSTEM is "CMD", that is "CMD" SYSTEM, a new cmd.exe interactive session will be started; close it with EXIT.

CD function changes the current directory of the running printf.exe process and was implemented because a CD command executed from CMD or SYSTEM functions returns to previous subdirectory when the CMD/SYSTEM function ends. Remember that when CD? is used as Test? it will be True if the directory is successfully changed.


The "filename" IN{?:n form of IN function open the given file for input, so posterior IN?:n operations (with the same N number) input lines from such a file. When the EndOfFile of the input file is reached, the file handle is closed and the IN? Test? is False. If the file does not exists, the open IN{?:n Test? is False. This is a simple example that show the contents of a file (similar to TYPE command):

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

The "filename" IN{+?:n form of IN function open the file for both input and output (update) access. In this case the same handle number must be used in both input and output operations: IN?:n and OUT:n. If you want to continue processing the file after an input operation reached the EndOfFile, read the lines with IN:n operation instead that do not close the file; in this case the EOF condition can be detected via the -1 value returned. The updated file must be closed with OUT}:n operation.

The "filename" OUT{:n form of OUT function create the given file (destroy contents if the file exists) and open it for output, so posterior OUT:n operations (with the same N number) send output to such a file. The OUT{+:n form preserve file contents if it exists, so the new output is appended to the end of the file. Both forms of open an output file must be closed with OUT}:n operation, and also the update file opened with IN{+?:n. If an output file can not be opened or created for any reason, the program is aborted.

This is a simple example that copy the contents of one text file into another one (similar to COPY command):

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


SEEK#:n:p function moves the file pointer of the input (#=0,1,2) or output (#=4,5,6) handle N from the origin indicated by the # digit (0/4 = from start of file, 1/5 = from current position, 2/6 = from end of file) plus the position (offset) given in the integer storage register P.

FINDSTR /O "^" filename.txt cmd.exe command returns the positions of the beginning of all lines in a file. A printf.exe program can get these numbers and use SEEK0:n:p to read the lines of a text file in random order. If a file have fixed-length records (lines), then the random access can be directly performed with no previous calculation of line positions, just with the calculation of: position = recordNum * recordLength. If a file is open with IN{+?:n update access, then it could be updated in just a few records and immediately close the file. If the file is big, this method could save a lot of process time (because it avoids to copy all unmodified file contents, as usually done in Batch files).

If P is not given, then the file pointer is moved to the point indicated just by #; in this case the final pointer position is returned in the stack as an integer number. For example, to get the size of a file:

printf "The size of file \"%s\" is: %u bytes\n" "filename.txt" IN{?:1 SEEK2:1

You can use this method to get the start of all lines in a file (instead of FINDSTR /O ... command), reading each line and storing the current position via SEEK1:n. You can read a more technicall description of SEEK capabilities at this page.


The OUT}+:n (Set size and close) form of OUT function change the size of the file to the current position of the file pointer. If you open/create an output file, move the file pointer beyond the EndOfFile and close it with Set size option, the file is increased to such a size. This behavior permits to create a large file in a very simple way. For example, to create a file with one hundred thousand bytes:

printf "File \"%s\" created with %u bytes\n" "filename.txt" OUT{:3 100000 ]5 SEEK4:3:5 OUT}+:3

The new space in the file is filled with binary zeros. However, if you open the file with Windows Notepad, the zeros will be converted into spaces.

If you move the file pointer of an output file before the EndOfFile and close the file with Set size, the file contents is truncated at such a position. For example, to truncate a large file at 10 Kb:

printf "File \"%s\" truncated to %u bytes\n" "filename.txt" OUT{+:8 10 1024 * ]2 SEEK4:8:2 OUT}+:8

You can also open the file with IN{+?:n (update access) in order to truncate it (preserving previous file contents). You can read more details about Set size option at this page.

Note that all these file handles are independent from each other. A program can open 10 input files and 10 output files managed via 0..9 handle numbers, plus 20 nested CMD input pipes.

Named Code Blocks (subroutines)

You can give a name to an external (not nested) code block. Doing that allows to enter (define) the code block just once and use it several times later. In standard programming languages this feature is called subroutine or procedure; we will also use such terms here.

The subroutine name is comprised of up to 4 characters (letters or digits only, starting with letter, ignoring case) that is placed before the BEGIN ( delimiter of the code block with no spaces between them. You can insert more characters for legibility, but only the first four comprises the name.

Definition of named code blocks must appear before the "format string". When you define a subroutine its operations are not executed, they are stored. To later execute or invoke or call the stored code block, just write its name in the same way as any predefined function. For example:

printf  First( "First" )  Second( "Second" )  /" "Two strings: %s - %s\n"  First Second OUT < <  First First OUT < <  Second Second OUT

The example show:

Two strings: First - Second
Two strings: First - First
Two strings: Second - Second

The subroutine name can not be the same of a predefined operation. If you do so, the subroutine is ignored and the name will always call the predefined function. Two subroutines can not have the same name either; remember that the subroutine name is just the first four characters.

A first use of this feature is to give a name to frequently used constants with the purpose of avoid magic numbers. When numbers are directly used there may be doubts about their meaning. If the constant has a descriptive name, its use is much clearer. For example:

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

In this way, a 10 that is used as a "number ten" can be differentiated from a 10 that represents an ASCII LineFeed character, so if the 10 must be changed later, such a change don't modify also the unrelated LineFeed. In printf - GetKey codes.txt file there are definitions as named code blocks of all special keys returned by GETK operation.

In the same way, you can give a name to a specification constant that is used multiple times in your program. When you want to change this specification, you must change its value in only one place (the subroutine definition) and the change will be reflected everywhere that such named constant is used. This makes program maintenance easier and helps avoid errors.

Another use of subroutines is to define commonly used code segments that will provide useful non-predefined features, or to divide a large program into logical units to increase the program's legibility. A simple example:

printf  Hypot( SQR XCHG SQR ADD SQRT )  "The hypotenuse of 3. and 4. is: %.2f\n" 3. 4. Hypot OUT

All the example programs shown in this document can be defined as subroutines and you could group they in a file that comprises a function library. This makes possible to just copy the desired ready-to-use subroutines to your program. Besides, named code blocks allows recursive invocations. A couple examples of recursive subroutines are shown next.

The classic example of recursion in programming is compute the factorial of an integer N, written N!, that is the product of all integers from N down to 1. For example: 5! = 5 x 4 x 3 x 2 x 1 that is equal to 120, or 4! = 4 x 3 x 2 x 1 that is 24. From these formulas is easy to see that 5! = 5 x 4! and, in general: N! = N x (N - 1)!; this is a recursive definition of a formula in terms of itself. Note that 1! = 1 that is the "end of the numbers", although, by definition, 0! = 1.

This definition is incomplete because it does not specify when the recursion ends. A more complete definition would be this one: N! = IF N==0 THEN 1 ELSE Nx(N-1)! (in C language notation: fact(int n) { return( (n==0) ? 1 : n*fact(n-1) ) }).

The way to implement this recursive formula in a printf.exe program is to check if N is 0, in which a case the result is 1 (change the 0 by 1 via increment and terminate); otherwise, multiply N by a recursive invocation of the same subroutine over N-1, that is: duplicate N, decrement it (N-1), get the factorial (N-1)! and multiply N x (N-1)!

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

Note that this example uses integer numbers, so it can calculate factorials up to the number 12. Also, note that this method can not be translated to floating point numbers because the FPU stack can only keep 8 numbers.


Let's review a different recursive example. As said before, the CMD function can be nested in several parallel and concurrent executions. We could make good use of this function to develop a recursive subroutine that traverses a directory tree. To do that, the subroutine only needs to display the subdirectories of the current directory. To display the complete tree, it is enough to call itself with each one of the found subdirectories. Here it is:

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

The subroutine TREE( starts by processing the subdirectories of the current folder using the function "DIR /B /A:D" CMD and then prepares the format FMT{ "%.*s%s\n" to be used. So, for each subdirectory read with ( 80 IN? <, output it to the screen with [0 [2 }3 OUT (more on that later), change the current folder with CD to get into that subdirectory (add 3 to register 0 with 3 ]+0 <*) and processes that subdirectory in the same way via a recursive TREE call. When a CMD's subdirectories are finished (that is, when a recursive call returns), return the current directory to the previous level of subdirectories with ".." CD (subtracts 3 from register 0 with 3 ]-0 <*) and the process continues with the following folder : from the previous CMD. When the first CMD finishes ), the format and the block of the subroutine FMT} ) are closed.

The "main program" does not use any "" format; just creates a string with 30 whitespaces ' ' 30 dupc that stores ]2 < in register 2 and calls TREE for the first time. This spaces string is used to display a justification margin using the format FMT{ "%.*s%s\n" (as described here) which increments in 3 spaces for each level of subdirectories found. The 0 register contains the margin value and the 3 elements (margin, spaces and subdirectory) are correctly shown with [0 [2 }3 OUT.

This example can be enriched by also showing the files in each directory, or by showing a justification margin made up of lines that more clearly marks the nesting of subdirectories (as in the TREE cmd.exe's command). The didactic form of this program, and the modification that also show the files, are in printf Example A - MyTree.bat file.


As usual in block programming, a named code block can also work as a Test? if a question mark is added at its end when the named code block is invoked; remember that you can not include any special character in the definition of a named code block. The method used to return a True or False result from the named code block is described in the next section.

Author's Note: Before moving on to the last section of this document, I would like to mention an important point. If you did not knew programming and have successfully completed the study of this manual up to this point, you should realize that you now have the foundations that will allow you to adequately study any modern programming language. The apparent simplicity with which these topics have been presented does not imply that the concepts themselves are simple; what is simple is the way to present them using the printf.exe scheme as an excellent educative tool.

If you have already programming experience I want to point out how simple is to write small code in block programming. The smart and simplified design of printf.exe allows you to obtain important results with very short code, supported by the extensive but easy-to-use set of predefined operations. Once the teething problems are overcome, and become accustomed to the unusual and somewhat cryptic notation of printf.exe expressions, it can be used to write programs that solve many of the everyday problems faced by personal computer users. I want to encourage you to continue using printf.exe in this regard.

Already registered as a printf.exe user? I suggest you take a look at the registration page before continue.

Boolean Operators

Advanced topic: In this section the last operational rule of printf.exe block programming is described.

Usually when a nested code block ends, the control flow continue normally to the next operation after the block. However, a nested code block can also work as a Test, in the same way as an Operator/Test combo. The way to enable this behavior is the same as before: just add a question-mark character to the ) block's END delimiter. For example:

printf "%s  %s\n"  ( "Normal block"  ( "This block works as Test" ; )?  /" < "Another" ) OUT

Simple! Isn't it? But in this case, how the nested code block condition will be True or False? The rule is really simple:

If the )? block's END delimiter is reached, the nested block is False; otherwise, the nested block is True.

This means that a nested block is True when the ; QUIT instruction is executed in it, or when a Test? inside the block was False and there is not any QUIT instruction ahead. In both cases the )? END delimiter is not reached, is skipped. You can visually review this rule in the rules scheme image at the beginning of this chapter.

In above example, the ( "This block works as Test" ; )? block terminate because the QUIT instruction, so the nested block is True. In this case the control continue, the < (Drop) operator eliminate the last string, the "Another" one is entered and finally OUT instruction shows: Normal block  Another.

If we delete the QUIT instruction from the nested block this way: ( "This block works as Test" )? then the block's END delimiter is reached, so the nested block is False. The control is transfered forward until exit from the base block, the OUT is executed and shows: Normal block  This block works as Test.

A larger example could be the animation program seen above modified in order to use conditional code blocks instead of a Flag:

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

rem Modified program that use conditional code blocks instead:
printf /" "\b %c"       100 ]1 <  219 ( OUT  ( GETK?:1  ( -75 ==? 10 ]+1 < ) <  ( -77 ==? 10 ]-1 < ) <  ( 13 ==? ; <  <  )?   )?    : )

In original program the whole process is repeated while F1 flag is True, and the action of Enter (13) key is just turn F1 off. Let's review the final part of modified program: ( 13 ==? ; < < )? )? : ) If the key is not Enter, execute two Drops and reach the end of the first conditional nested code block, so it ends with False result. This result cause to skip after the end of the second conditional block, so the : REPEAT is executed and the whole process is repeated again. When the key is Enter, the ; QUIT is executed and the control jumps after the block END, so the END of the second conditional nested code block is reached and its result is False. This result cause to skip after the final : REPEAT instruction and the process ends.

This same behavior apply to a named code block (subroutine). If a subroutine is called as a Test? (with a question-mark character added at end of its name), then its True-False result is given by the way the named code block ends: if the ) block's END delimiter is reached, the NAME? subroutine is False; otherwise, the NAME? subroutine is True.

If we analyse it, we will realize that this behavior gets the opposite result of a Test? placed inside a (block)? when there is not any QUIT instruction placed after the Test?: if the Test? is True the (block)? is False, if the Test? is False the (block)? is True. This mechanism allow us to assemble the standard Boolean operators (AND, OR and NOT) in a very simple way.


NOT Boolean operator: ( Test? )?

If Test? is True the control flow continue and reaches END delimiter, so the result of the "NOT" block is False.

If Test? is False the control flow is transfered forward until pass the END delimiter, so the "NOT" block is True.


AND Boolean operator: ( Test1? Test2? ; )?

If Test1? is True the control flow continue:
- If Test2? is True the control flow continue, so QUIT is executed and the (block)? result is True.
- If Test2? is False the control is transfered forward until pass QUIT, so END is reached and the block result is False.

If Test1? is False the control is transfered forward until pass QUIT, so END is reached and the block result is False.

In other words: only when all conditions are True, the "AND" block result is True; this behavior can be easily extended to more than two tests/conditions. The "AND" block works evaluating the Tests? in series and stop at the first one that is False.


OR Boolean operator: ( Test1? ; Test2? ; )?

If Test1? is True the control continue, QUIT is executed and the block result is True. If Test1? is False the control is transfered forward until pass next QUIT, so Test2? is evaluated.

If Test2? is True the control continue, QUIT is executed and the block result is True. If Test2? is False the control is transfered forward until pass next QUIT, so END is reached and the block result is False.

In other words: if any condition is True, the "OR" block result is True; only when all conditions are False, the "OR" result is False. This behavior can be easily extended to more than two tests/conditions. The "OR" block works evaluating the Tests? in series and stop at the first one that is True.


You should note that AND and OR Boolean operators assembled in this way have an implicit Short-circuit evaluation: as soon as the result of the block is known (False for AND, True for OR), the rest of tests in the block are not evaluated.


In the case of XOR Boolean operator, there is not a way to assemble it as simple as before. The result (truth table) of XOR operator over Test1 and Test2 conditions is this:

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

XOR result can be obtained via the following expression that only includes AND, OR and NOT Boolean operators: (Test1 OR Test2) AND NOT (Test1 AND Test2). The translation of such an expression into block programming is this:

(  (  Test1? ; Test2? ; )?  ( (  Test1? Test2? ;  )? )?  ;  )?
 \  \__Test1_OR_Test2__/     \ \_Test1_AND_Test2_/  /   /  /
  \                           \_NOT_the_above_AND__/   /  /
   \                                                  /  /
    \__The last QUIT completes the AND of these blocks__/

In the case of XOR Boolean operator, it is not easy to apply it to more than two tests. If we extend the XOR concept to more than two operands, then the result is True when precisely one of the conditions is True, and False in any other case. Note that this is not the same of apply XOR several times over two operands or partial results each time. For example, a XOR with 3 tests/conditions:

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
 
 
 
 
 
 
 
 
 
 
 


An interesting exercise is to define the Boolean expression that obtain this result using just AND, OR and NOT operators. If you give up, here it is a possible solution:

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

This expression could be assembled by inspection. In the top half of the table (where T1 is True) there is just one True result: where there is not T2 nor T3. However, the boolean expression could become harder to write as the rest of result values needs to be included.

We could simplify this expression a little if we also use the XOR operator over two operands seen before:

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

The reader is invited to convert these Boolean expressions into RPN block programming code.

Top

Advanced Graphical Examples

In this section several printf.exe advanced example programs are described. These programs share a common point: they all are based on graphics, that is, on drawing figures on the screen. This topic was choosen because these programs are more interesting than other types of advanced applications and the required calculations are good examples of the power of printf.exe as a programming language.

If we take into account that printf.exe is a text-based command-line program, how we could draw a graphic using it? Well, rudimentary graphics can always be drawn using characters, and the drawn figure will be more detailed as the character size be smaller. In this way, in order to draw perfect graphics in text mode, you just need to use one-pixel size characters and that is it! This method is described with detail at this thread. We will use the PIXELFNT.EXE utility in order to enable the 1x1 size font ("graphics mode").

The Mandelbrot Set

The Mandelbrot Set is the most famous figure of the family called Fractal Curves. It is an interesting graphic full of recursive details as shown at right side that is drawn based on a relatively simple method, although the mathematic concepts involved are not simple at all.

Take the screen as a complex plane of Z=X+iY coordinates. At each ZPos=(XPos,YPos) point iterate the value of Zn+1=Zn^2-ZPos [that is equal to (Xn+1=Xn^2-Yn^2-XPos) + i(Yn+1=2*Xn*Yn-YPos)] until the absolute value of |Zn|=SQRT(Xn^2+Yn^2) is greater or equal 2. The number of iterations gives the color of that point. If |Zn| don't reach 2 after a maximum number of iterations, the point remains black. Simple! Isn't it?

The C code below exemplify the method.

/*  X and Y are "for" integer variables with coordinates for the window; for example: 640 x 480 pixels  */
/*  Note that if the number of pixels exceeds the screen size, the window will have scroll bars         */
/*  XPos and YPos are floating point graphic coordinates that covers the image area                     */
/*  For example: XPos from -1 to +2 and YPos from -1.125 to 1.125 (to keep the 4:3 screen aspect ratio) */

MaxX = 640;
MaxY = 480;
          YTop = 1.125;
XLeft = -1.0;     XRight = 2.0;
       YBottom = -1.125;
MaxLevel = 500;         /* This value set the maximum number of iterations at each point     */
                        /* Larger values produce more detailed graphics, but takes more time */

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;

      /* Iterative calculation of Level at this (XPos,YPos) point */
      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); }
   }
}

In the real program it is necessary to map the Level range from 1..MaxLevel into the range of colors on the screen; for example 1..15 in text mode. This mapping is done by grouping the highests levels together in every time larger groups, producing wider color bands. The grouping of Level into colors is done this way (this is the best simple method after several tests):

if (level <= 12) {		// Color = level in first 12 values
   color = level
} else if (level <= 14) {	// Group 2 levels in next color
   color = 13
} else if (level <= 18) {	// Group 4 levels in next color (yellow)
   color = 14
} else if (level <= 36) {	// Group 18 levels
   color = 1			// in a dark blue band
} else {
   color = 15			// Group the rest of levels in white color
}

The method shown below is the most efficient way to perform the previous calculation of the Level at each (XPos,YPos) point using the FPU. Note that this code do an extensive use of FPU stack-only operations.

In RPN code, working values are stored on these storage registers:

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

This is the working configuration of the FPU stack registers used in the iterative calculation:

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

with 4 empty registers to perform operations

LEVEL(          /* This procedure get the level of current YPos XPos screen position in Integer register 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 that RCL6 is a *standard* load number operation, so it should use standard DROP    */ ^
   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?        /*"Is --Level > 0 ?"    */ ^
   :            /* LOOP                 */ ^
   DUP:1 DUP:1  /*            B=YPos     A=XPos     T=Yn+1     Z=Xn+1      Y=Xn+1      X=Xn+1    Same stack when EXIT */ ^
   )            /* ENDWHILE             */ ^
   DROP:1 DROP:1 DROP:1 DROP:1          /* Recover initial stack:          Y=YPos      X=XPos              */ ^
                /* Map the Level into a 0..15 text color number: 0..12:same, 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        */

The complete program is in printf Example C - Mandelbrot Set.bat file.

Turtle Graphics (Logo)

In 1967 the Logo Language was designed as a computer programming language with didactic purposes. The most remarkable aspect of Logo is its "Turtle Graphics" feature that can be briefly described this way: These graphics are drawn by a "Turtle" that moves around the screen dragging a "pen" attached to its tail. You can control the turtle by giving it orders or commands to walk forward or backward and to turn left or right, so turtle movements draw lines with the pen on the graphic window. The turtle can also rise its tail (penUp command) so posterior turtle movements don't draw lines anymore until a posterior penDown command is given.

Currently there are many distincts Logo dialects and the Turtle Graphics feature is used in several different applications and places. You can find a lot of information and implementations of Logo in the web, like this one or that one that also includes complete Logo language lessons. However, the simplest way to take a first contact with Turtle Graphics is ready for you right now.

In this chapter a Turtle Graphics implementation as an example program for printf.exe application is presented, so just execute it, read the simple instructions and start giving commands to the turtle. The only detail you must pay attention to is include a decimal point in the number of degrees to turn "left" or "right" (floating point number), whereas all other numbers must be integers. As usual in printf.exe program, if you give the wrong type of number the program will fail with no warning message.

The image at right side show the graphics that can be obtained from the Turtle Graphics printf.exe example program.

Recall and edit command lines

When you give commands to the turtle you sometimes make a mistake you may want to fix, or perhaps you may want to repeat a previous line with a different value in some parameter. This can be easily done because all the commands you execute at the command line and in printf.exe application are stored in a "command history" list you can review in order to recall a previous line to execute it again, or to modify it before be executed. The keys shown in the next table allows you to perform such command lines edition.

Edition keys in the command line
Command line selection: Cursor movement in the line: Edition of lines: Scroll window text:
PageUp First (top) line Home To begin of line Ctrl-Home Erase from cursor
to beginning of line
Ctrl-Home Scroll window
to top line
UpArrow Previous line Ctrl-Left To left one word
DownArrow Next line LeftArrow To left one char Ctrl-End Erase from cursor
to end of line
Ctrl-Up Scroll window
one line up
PageDown Last (bottom) line RightArrow To right one char
F7 Menu with all lines Ctrl-Right To right one word F3 Recover end of line Ctrl-Down Scroll window
one line down
F9 Recall number line End To end of line Esc Erase whole line
Chars+F8 Seek line(s) that
start with Chars
F2+Char Seek the char Ins Toggle insert or
replace modes
Ctrl-End Scroll window
to bottom line

You can also press Ctrl-M (Mark) to select a text from the screen moving the cursor with Shift key pressed, or moving the mouse with the left button pressed, and press Enter to copy the selected text. Then, you can paste the text at the command line with Ctrl-V or via a mouse right-button click. You also can press Ctrl-F (Find) to search for a text that may appear in a previous window page. The documentation of these features appears in MS doskey command page.

Advanced topic: Below there is a more technical description of the implementation of Turtle Graphics in printf.exe.

The example program of Turtle Graphics contains all subroutines for the commands described in the table below. However, this program is also an easy-to-use example on the use of such commands. For this reason the program is large and most of its code is devoted to its use as an interactive example. The Turtle Graphics support subroutines are contained in just a few lines located at the end of the file. If you want to use such commands in a printf.exe RPN program of your own, just copy such subroutines to your program.

Working Parameters
Reg Parameter
I0 X position
I1 Y position
I2 Draw color
I3 Back color
I4 Window X
I5 Window Y
I6 Font width
I7 Font height
I8 Font X size
I9 Font Y size
   
FP0 Angle
F0 Pen Down/Up
Turtle Graphics Commands
Parameters Command Description
pixels forward fd Moves the Turtle forward; negative values go backward.
pixels back bk Moves the Turtle backward; negative values go forward.
degrees. right rt Rotates the Turtle clockwise; negative values rotate counterclockwise.
degrees. left lt Rotates the Turtle counterclockwise; negative values rotate clockwise.
  penUp pu Put pen up: following Turtle movements don't draw lines anymore.
  penDown pd Put pen down: following Turtle movements draw lines again.
color setColor sc Set the color of the drawing. Colors are limited to values in 0..15 range.
color setBack sb Set the background color of the graphics window.
  home Moves Turtle to center of window, pointing upwards.
  clearScreen cs Clears the graphics window and resets all parameters.
line col setPos sp Moves Turtle to given (Y,X) position.
Center of window is at (0,0) position.
degrees. setHead sh Turns Turtle to an absolute heading: 0=upwards, +=clockwise.
"filename.fdf" setFont sf Load a Font Definition File.
sizeY sizeX setSizes ss Set vertical and horizontal multiplication factors for characters in font.
"text" label Show a text message ("label") with current font and size.

Most commands in previous table are standard Logo Turtle Graphics commands that works in the same way, with the obvious difference that these commands uses the postfix RPN notation (with the command placed after the parameters instead of before), and the difference in the form to write operations or loops. Almost all commands have a two-letters abbreviation that can be used to write shorter expressions when the original commands were comprehended. For example, this standard Logo drawing:

repeat 12 [  forward 190   right 150  ]

... can be traslated to printf.exe Turtle Graphics in this way:

       12 (  190 forward   150. right  --? :  )

... or in this shorter way:

       12 (  190 fd   150. rt  --? :  )

You can review a series of Turtle Graphics figures translated from Logo to printf.exe RPN code in the second part of the BATch Turtle Graphics source example program.

IMPORTANT: Remember that right, left and setHead commands requires that the parameter be a floating point value (with decimal point). These commands will fail with no warning if the parameter is an integer number.

The implementation keeps a series of working values in storage registers as shown in the table at right side above. For example, Flag 0 keep the pen status: True=penDown, False=penUp. This Flag is tested (F0?) by forward and back routines to know if the line must be drawn or just the Turtle position is updated. This means that the implementation of penDown/penUp subroutines is very simple:

penDown( SF0 )		penUp( CF0 )

In a similar way, right/left subroutines just modify the angle stored in floating point storage register 0:

right( STO-0 DROP )	left( STO+0 DROP )

setBack subroutine have a somewhat strange behavior because it was implemented via COLOR cmd.exe's command, so it losts the color of all already drawn figures and revert they to the current drawing color. Also, you can not set a background color the same as the drawing color; you must change drawing color first. It is suggested to use setBack command at beginning, before any figure has been traced.

hideTurtle and showTurtle commands don't exists; they are simulated in the BATch file code via a "blinking turtle" that is achieved using the XOR drawing line mode described below. In the interactive use, clearScreen command should appear alone in a line; otherwise the "blinking turtle" will fail.

All previous commands are programmed in RPN printf.exe source code; you can review they in the source program. Some of these commands (forward, back and label) use other support functions that are programmed in assembly language inside printf.exe code as predefined functions in order to improve the efficiency.

Turtle Graphics Support Predefined Functions
Parameters Function Description
pixels fdMov Calculate the Ydisp and Xdisp displacements to move the turtle pixels forward towards the heading stored in floating point register FP0.
Ydisp Xdisp line If Flag 0 is True: Draw a line with the color given by I2, from the origin stored at integer registers (I0,I1) to a new point displaced (Xdisp,Ydisp) pixels. At end the new position is stored in I0,I1 and the Y,X displacements are dropped out from the stack.
Ydisp Xdisp Xline Draw a line in the same way as line but performing a XOR operation between the color of the pixels already on the screen and the drawing color in register I2.
base poly Draw the polylines defined on the integer registers that start at the index stored in the base register given in the parameter.

Using these predefined functions is easy to define forward and back commands:

forward( fdMov Line )		back( _ fdMov Line )

Turtle Graphics Commands setFont, setSizes and label, and the predefined function poly will be further described in the next chapter.

Turtle Graphics Labels

If we are using the 1x1 pixels size font to show graphics, how we could show text in the screen? There are several possible solutions. The most basic one is the method originally used by the old MS-DOS Operating System that read a Font Definition File that define the individual pixels that must be turned on to form each character (Raster Font). You can read a description about an example program that use this method at this site. However, the method have the disadvantage that characters looks "pixeled" if their size is increased a lot, as you can see in the image at right side.

Another, more modern solution consist in use vectors (elements with both magnitude and direction) to define the characters drawing in a similar way of Turtle Graphics, so letters and symbols keep its appearance even if their size is increased. This is the method that Windows Operating System uses currently, so we can see a lot of "True Type" and other similar fonts. Although this is the right solution to show characters in "graphics mode", the processing of True Type files is complicated because they contains a lot of advanced elements that are required by modern text processing applications.

An intermediate solution consists in using vectors in the simplest possible way, defining each straight line just by a X and Y axis displacements couple even if doing this limits the way the characters can be drawed. For example, characteres defined this way can not be drawed at any arbitrary angle; just in multiples of 90 degrees. In the implementation of the routines in charge of show text the same scheme used by Logo Turtle Graphics was adopted, so there is a setFont procedure that read a font definition file, a setSizes procedure that sets the vertical and horizontal font sizes, and label procedure that show a text message ("label" in Logo terminology). The label procedure in turn uses the predefined function poly in charge of displaying a series of line segments ("poly lines") defined as a series of (Y,X) displacement pairs stored in consecutive integer storage registers. The characters of each font are defined in a text file with a .fdf extension (Font Definition File) that can be created with any text editor. The package includes two .fdf files: APA Big Font 7x12.fdf (that appears in the image at right side) and APA Small Font 3x6.fdf, designed to show characters in big and small sizes, respectively.

A detailed description of the format of these files and the process that setFont and poly procedures performs appears in the Turtle Graphics Labels example program. As happened with the program of the previous chapter, this is also a complete example that allows for a simple use of these facilities, so the code is extensive. The printf.exe procedures for showing text appears in a small part at the end of the file. If you want to use these routines in your own printf.exe programs, just copy such procedures. The poly procedure that appears in the program is completely functional; however, there is a predefined function with the same name that perform the same task, so copy such a procedure is not necessary.

Recursive curves

A very interesting application of graphics is the drawn of Recursive Curves. This is a family of figures based on several mathematical concepts that show a series of appealing looks. Some of these curves are Hilbert, Minkowski, Wirth, etc.

This example program is currently under development. You can read a description about the method used at This site.
Top

Appendices

printf.exe installation and support

The installation of printf.exe package is very simple and just consists in download the printf.zip file and extract its contents in a given folder, so no status nor registry in the computer is modified.

This is a summary of the files included in the package:

AlgebraicToRPN.bat		Converts algebraic expressions into RPN
GETARGS.obj			- Used in Appendix 2
GetStandardForm.bat		Converts printf.exe Didactic Form to shorter standard form
PIXELFNT.EXE                    Allows to draw graphics	via 1x1 pixel size font
printf - GetKey codes.txt	Codes returned by GetKey function
printf Example *.bat		Several printf.exe example programs in .BATch files
printf.exe			The printf.exe application
printf.exe - User's Manual.html		English user's manual
printf.exe - Manual de Usuario.html	Spanish user's manual
printf.obj			- Used in Appendix 2
printfHelp.bat			printf.exe on-screen help
printfMake.bat			- Used in Appendix 2
printfStorage.asm		- Used in Appendix 2
Register your copy of printf.exe.html	English register page
Registra tu copia de printf.exe.html	Spanish register page
ShowKeyCodes.bat		Generate GetKey operator codes

It is suggested to start using this package from the same folder where it was installed. Later, you could include the printf.exe full path into PATH system variable in order to use this application from any other folder.

NOTE: Some antivirus applications flags printf.exe as "potentially dangerous" or "positively infected". This is a false positive message caused mainly because the application is written in assembly language, something unusual these days. If you downloaded the printf.zip package file from apaacini.com site, then it is virus free for sure. The first time that you execute this program, Windows flags it with a warning message about the risk of executing programs downloaded from the web. Just answer: "Execute it anyway".

If you have any question or doubt about printf.exe usage, you can review DosTips.com printf.exe page. Read the thread from the beginning; it is likely that your question had been posted and answered before. Otherwise, just write your question in a clear way and post it as New Reply; include an example of the printf.exe operation you have problems with. It is convenient that you register as user in DosTips.com site before post your question.

It is also suggested that you register as printf.exe program user.

Increasing storage space

The number of storage registers, both integer and floating point, and the number of characters reserved for strings can be increased to any value you wish following the procedure described in this section; however, if these values are excesive and the computer have not enough memory, a run-time error will be issued.

You can also increase the number of nested CMD functions, the number of nested FMT{ marks, and the number of named code blocks (subroutines) that can be defined.

You must start by installing the MASM32 assembler. Open the MASM32 SDK link and complete the download and installation process; this should take just a couple minutes. When process is completed, the assembler should be installed in its default folder: C:\masm32\

The values that sets the printf.exe storage amounts are defined in printfStorage.asm file. You must edit such a file with a pure-text editor, like Windows Notepad. If you use another text editor, be sure that the file be saved as text with no format and ANSI encoding. These are the original values you can modify:

	IREGS		EQU	20		;Number of Integer storage registers
	FREGS		EQU	20		;Number of Floating Point storage registers
	CHARS		EQU	10 * 1024	;Size of memory block for string data = 10 KB
	MAX_FORMAT	EQU	40		;Maximum nested FMT{ marks x 2
	MAX_CMDSTDIN	EQU	20		;Maximum number of nested CMD functions (redirected Stdin's)
	NAMEDBLOCK_LEN	EQU	32		;Allowed number of "namedBlocks" (subroutines)

You can also review and modify the letters case conversion table defined in this file, but you must understand first how the values on the table were defined.

Do NOT modify any other part of this file. After the modified file is saved, open a cmd.exe window and execute printfMake.bat program just once. That is it!

printf.exe application also allows to retrieve the first three values. For example:

printf "Integer Registers: %i\tFloat Registers: %i\tCharacters: %i\n" IREGS FREGS CHARS

This feature makes possible that a program check if there is enough memory for its operation and, if not, show a descriptive message and cancel process instead of cause a run-time error. For example:

printf "%s\n"    ^
   ( 1500 IREGS <?   ^
      "Error: The number of integer registers must be incremented to 1500" OUT   ^
   ;   ^
      "Enough registers, proceed..." OUT   ^
      1 2 3 ETC...   ^
   )

Error Messages

In this appendix the error messages of printf.exe application are described. When one of these errors happens, printf.exe ends with an ERRORLEVEL value equal to the error number with negative sign.

  1. NOT ENOUGH MEMORY!

    - The computer have very little available memory (very unlikely).
    - You have requested a very high number of storage registers/characters.

  2. Unclosed 'Char, "string or /*comment

    The closing character of a Char', String" or Comment*/ is missing. Note that an unclosed string or comment can only be detected until the program ends.

  3. Wrong or too large number

    A number is bad written. Refer to Data Types for the proper syntax.

  4. Invalid operator

    This error is usually a typo. Remember to separate all operators with one or more spaces. This error may also happen when a "long" operator is bad written, like ]--.3?.

  5. Undefined function or variable

    This error is usually a typo. Check the spelling. This error may also happen when a "long" function is bad written, like IN{+?:6. Remember that if you use a Batch variable value as a string, such a variable must be defined.

  6. Maximum number of operations exceeded

    More than 20 levels of active FMT{ marks or nested CMD functions, or more than 32 Blocks with Name (subroutines) defined. See the Appendix 1 for instructions about incrementing these limits.

  7. ":" Repeat, ";" Quit, ")" End or false Test? outside any (Block

    These operations: : ; ) and a Test? that is False can only be used inside a code block.

  8. Unclosed Block at nesting level #

    The right parenthesis of an open code block is missing. Note that this error can only be detected until the program ends. The reported block's nesting level can help to identify the unclosed block.

  9. This NAME( Block can not be defined here

    The named code blocks (subroutines) must be defined before the "format" string and must be external (not nested) code blocks. Two named code blocks can not have the same name (first 4 characters).

  10. Too many (recursive) subroutine calls

    This error is usually a loop of recursive invocations with no exit. Check the condition that breaks the recursive invocations cycle. The maximum number of pending subroutine returns is about 128,000.

  11. Can not create/open output file

    An output file tried to be opened was not found or can not be created. This is usually a typo that inserts an illegal character in the file name, or a non-existent subdirectory. Remember that you can create a directory via SYSTEM function. The same "not found" error on an input file is detected in the IN{?:n open operation/test? itself.

  12. Input/Output operation over a not open handle

    An input IN?:n or output OUT:n file operation uses a handle that was not previously open. Check that the N handle number used in open and I/O operations be the same.

After printf.exe show an error message, you can show its corresponding description executing this line: printfHelp %errorlevel% You can insert this line in an ERR.BAT file and then just execute: ERR


Introduction Formatted
Output
Arithmetic
Operations
Block
Programming
Register your
printf.exe