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 simply 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 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 neglect the more technical aspects of this knowledge. Don't forget that creating a program that works correctly doesn't have to be "fun", but rather 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 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 function printf 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 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 function and HP calculators.
The advanced usage of printf.exe includes string handling, so it can be applied to many word processing tasks. In addition, the advanced version of printf.exe features the simplest programming scheme that exists, 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 40 years. This point allows the user to learn basic programming principles almost as easily as learning to perform arithmetic operations in the HP calculator.
More importantly, printf.exe's advanced programming scheme allows it to emulate all the classical figures of structured programming in modern programming languages, but it is based on just four elements and a couple simple rules about evaluating conditions. This is a very simple scheme to understand and learn, yet it provides the same insights that are required while using a modern structured programming language.
The extensive documentation does not assume any prior knowledge of these topics so the examples are straightforward and the explanations are detailed. The user 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; just a little interest in math.
For a better experience, it is suggested to register your copy of printf.exe program.
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". This is a false positive message. If you downloaded the printf.zip package file from apaacini.com site, then it is virus free for sure.
The general syntax of 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 right-click; 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 careful 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!
Top
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 and \"
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 is 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.
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) | ||
F | double (like f, capitalized) | ||
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 |
Data format specification: %[flags][width][.precision]type
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
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, 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. 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. 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 numbers are separated by a simple 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 file 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 (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.
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":
%i
format specification (and vice versa: show integer (code) as character using %c
format specification).
printf "%i\n" 'a' 'A' -
printf "%c\n" 'a' 'A' - 'X' +
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 Functions.
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>"
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.
All floating point operations are specified via a word of a maximum of 4 characters, that we call 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 INIT |
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
.
INIT function initialize (clears the stack of) the X87 Floating Point Unit (FPU). See description below.
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 adressed via a 0..9 digit inserted in place of N letter in the storage registers operations below, or via the point-digit combination that adress registers 10 to 19; for example: ]3
or STO.5
(in the table X refers to the last entered number).
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 adressed through the i
Index Register as explained in Section 10: The Index Register of the HP-15C Owner's Handbook, that is 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.
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 | |
]%n |
STO%n |
Store-Mod | RegN = RegN%X | ... and get the remainder | |
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 | |
[%n |
RCL%n |
Recall-Mod | X = X%RegN | ... and get the remainder |
Note that [++n and [--n integer "Recall" operations works the same as the simpler ++ (Increment) and -- (Decrement) ones.
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 S+ 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 | Sx | Summation of x-values. |
R4 | Sx^2 | Summation of squares of x-values. |
R5 | Sy | Summation of y-values. |
R6 | Sy^2 | Summation of squares of y-values. |
R7 | Sxy | 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.
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.
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 |
10. 20. 30. 10. 30. 40. 20. 30. 40. |
Note that >1 is the same as >, that <1 is the same as <, and that <>2 is the same as <>; the <>1 operation is not valid.
<* (Drop All) operation eliminate all data inserted after the format string.
The { } (RollDown/RollUp) operators can not be used without a position and such a position must be greater or equal 2 (like Exchange). Roll Down { move last number to a previous position (similar to store). Roll Up } move number in previous position into last number (similar to recall).
Remember that printf.exe can only keep up to 8 floating point numbers; this means that the position in DUP must be in 1..7 range, in XCHG must be in 2..8 range, and in DROP in 1..8 range. There is not RollDown/RollUp functions for floating point numbers in current version of printf.exe program.
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. }4 /* Fixed: rotate the *4th* integer (there are 2 integers and one float) so the integer 10 is moved to the last position */
Int: 20, Float: 30.00, Int: 10
printf "Float: %.2f, Int:%i, Int: %i\n" 10 20 30. {4 {4 /* Rotate the two halves of the 30. to a previous 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.
Advanced topic: This section contains several technicall descriptions.
The printf.exe program uses the Floating Point Unit (FPU) to perform floating point arithmetic operations; this is a part of the computer specifically designed to evaluate such operations. The FPU uses a stack of 8 registers entirely similar to the stack of HP calculators.
When printf.exe program gets a floating point number in the parameters it pushes the same number in the FPU stack, so both areas (parameters and FPU stack) usually contains the same 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 INIT operation that clears the FPU stack, but don't touch the parameters. Of course, this point also imply that after an INIT operation you can NOT perform arithmetic operations on the numbers that were entered before the INIT.
There are a couple operations that also execute a FPU INIT, like <* (Drop All) and FMT} (Format End); they will be described later.
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) operation do liberate the data area of all deleted strings, so such an area could be used by any new string entered after.
Name | Description Canonical form (CRT) |
Example | ||
---|---|---|---|---|
Before --> | func | --> After | ||
atoi | Convert a string to one/all integer number(s) atoi0(string) atoi1(string) |
"123" "12 34 56" |
atoi0 atoi1 |
123 12 34 56 3 |
atof | Convert a string to one/all floating number(s) 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" |
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 shift(str1,str2,...,strN,N) |
"This" "is" "a" "test" 4 | shift | "is" "a" "test" 3 "This" |
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: -1 0 1 cmps(str1,str2) cmpsi(str1,str2) |
"ONE" "One" | cmps cmpsi |
"ONE" "One" 1 "ONE" "One" 0 |
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.
cmps function gets the "ordinal relation" of the strings and returns -1, 0 or 1 if the first string is less than, equal or greater than the second one, respectively.
The standard operation of index, repl and cmps functions is case-sensitive: the case of the substring must match the case of the base string. You can ignore the case of letters in cmps function adding an "I" letter at end: cmpsI (only in this function in this version of printf.exe).
The movc function with "pointer" type parameters is described below.
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" 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 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 (inserts) a character into a string.
movc2 is an advanced function 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 between the function and its last digit, like in movc+0
or movc+1
. In the last variant you can insert a "plus" sign before and after the "2" to increment each one of the pointers, or even to increment both pointers like in movc+2+
. This behavior aids to write compact and efficient loops that processes several characters. Some comparative examples of these features will be described later.
Top
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 method 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:
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
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.
Oper | Name | Description |
---|---|---|
GETK |
Get Key | Get one key (character) from keyboard |
IN |
Input Line | Read a line (string) from keyboard |
OUT |
Output | Show current data with current format |
FMT{ |
Format start | Allows to enter a new format and new data |
FMT} |
Format end | Removes new format and data from previous FMT{ mark on |
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
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 an INIT operation. If you want to preserve a floating point number to use it after a FMT}, then you must keep it in a storage register.
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.
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.
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.
A test is an operation that, when it is evaluated, 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.
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.
Operation | Perform |
---|---|
? |
Test integer |
]n? |
Test storage register N |
--? |
Decrement |
]--n? |
Decrement storage register N |
!? |
Boolean NOT |
&? |
Bitwise AND |
%? |
Division remainder |
getc? |
Get char from string |
movc? |
Move a character |
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.
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.
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:
@echo off
at beginning of the file.
%
characters by double-percent: %%
^< ^> ^| ^&
escaped character by the use of Quoted switch, like /" < /"
, or /" > /"
, etc.
/*
and */
(Comment) delimiters.
^
(caret) character, except after the last one. Be careful to not insert any space after the caret.
/"
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
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.
The Standard Numeric Tests below compare the last number (that we call "X") versus zero (with integer and floating point versions), or compare the last "X" number versus the previous one (called "Y"), also with integer and floating point versions.
One-operand tests | Two-operands tests | ||||
---|---|---|---|---|---|
Int | Float | Condition | Int | Float | Condition |
>0? |
GTR0? |
X greater than 0 | >? |
GTR? |
X greater than Y |
>=0? |
GEQ0? |
X greater or equal 0 | >=? |
GEQ? |
X greater or equal Y |
<0? |
LSS0? |
X less than 0 | <? |
LSS? |
X less than Y |
<=0? |
LEQ0? |
X less or equal 0 | <=? |
LEQ? |
X less or equal Y |
==0? |
EQU0? |
X equal 0 | ==? |
EQU? |
X equal Y |
!=0? |
NEQ0? |
X not equal 0 | !=? |
NEQ? |
X not equal Y |
For example, to get the MAX of two integer numbers, use ( >? <> ) <
, 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: ( <? <> ) <
.
To get the MAX of two floating point numbers, use ( GTR? XCHG ) DROP
, and to get the MIN: ( LSS? XCHG ) DROP
.
You can also use =0?
and =?
for "X equal 0" and "X equal Y", or <>0?
and <>?
for "X not equal 0" and "X not equal Y" 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 <=? and >=? integer tests:
printf "%s\n" "Hello, World!" /" 0 ( getc? ( 'a' <=? < 'z' >=? < 32 - putc 'c' 'x' ) < < ++ : ) < < OUT
á - 160 é - 130 í - 161 ó - 162 ú - 163 ü - 129 ñ - 164 ¿ - 168 |
Á - 181 É - 144 Í - 214 Ó - 224 Ú - 233 Ü - 154 Ñ - 165 ¡ - 173 |
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 upcase and lowcase 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 foreign 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.
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.
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 operation 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.
Operation | Function | Description |
---|---|---|
Standard output | OUT |
Send formatted output to the screen via "format" data1 data2 ... |
Format start | FMT{ |
Start a new format and data for OUT, up to end it with FMT} |
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 register |
Standard input | maxlen IN |
Read a line from keyboard |
Execute a 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 # |
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 the command parameter of CMD is "CMD", that is "CMD" CMD
, a new cmd.exe interactive session will be started; close it with EXIT command.
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 )
Origin | Input | Output |
---|---|---|
From start | SEEK0 | SEEK4 |
From current | SEEK1 | SEEK5 |
From end | SEEK2 | SEEK6 |
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. The table at right side show the six forms of SEEK# function.
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.
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. 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. 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 constant is used.
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 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, know 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 already had programming experience I want to point out how simple it is to write small code in block programming. With very few elements important results can be achieved, which is a result of the design of printf.exe and its predefined operations. Once the teething problems of printf.exe's unusual and a little cryptic notation are overcome, 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 right now.
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) )
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 block programming code.
Top
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 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 - Arithmetic and Programming.html English user's manual printf.exe - Aritmética y Programación.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 programs flags printf.exe as "potentially dangerous". This is a false positive message caused, in first place, because the source code of printf.exe is written in assembly language. If you downloaded the printf.zip package from apaacini.com site, then it is virus free for sure.
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.
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)
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 are 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... ^ )
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.
- The computer have very little available memory (very unlikely).
- You have requested a very high number of storage registers/characters.
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.
A number is bad written. Refer to Data Types for the proper syntax.
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?
.
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.
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.
These operations: : ; )
and a Test? that is False can only be used inside a code block.
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.
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).
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.
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 CMD function. The same "not found" error on an input file is detected in the IN{?:n open operation/test? itself.
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