Postscript III:The Operand Stack of PostScript: Arrays, Variables, Loops and Macro Definitions

ArticleCategory: [Artikel Kategorie]

Software Development

AuthorImage:[Bild des Autors]

[Photo of the Author]

AuthorName:[Name des Autors]

Emre Demiralp

AboutTheAuthor:[Über den Autor]

I am a student in Istanbul American Robert College, and at the same time, one of the administrators of the Computer Labs in Sciences and Letters Faculty of Istanbul Technical University. Overdominating operating system in these labs is LINUX. Interests: PovRay and PostScript, animation, CD design, programming, holography etc.. Linux user since 1994.

Abstract:[Zusammenfassung]

One who knows too much also fails too much
-Turkish Proverb-.

The author continues to describe the operand stack of the PostScript language. The definition of arrays, the array operators, the variable definitions, the loops and the macro definitions all are presented here with sufficient detail and illustrative examples and questions which will be answered in the next article. This article does not complete to tell about the operand stack. The story will continue in the coming articles.

ArticleIllustration:[Titelbild des Artikels]

[Ilustration]

ArticleBody:[Der eigentliche Artikel]

Introduction

This is the third of a series of articles about PostScript. We continue to tell about the operand stack of the PostScript. We emphasize on the definition of the arrays, array operators, variable definitions, loops and macro definitions. We try to give important points about these subjects. The presentations is enriched by certain illustrative examples also. The story of the operand stack will continue in the future articles on different topics related to the operand stack.

Arrays and Array Operators

In the previous articles of this series we mention about the structure of the operand stack and the operators which can change the operand stack's structure. As we remember, all the quantities which are stored in the operand stack were integers except a special element which is used to create referrence point(s) in the operand stack. This element was called -marktype- and the operators cleartomark and counttomark were being used to clear or count the elements of the operand stack from top to this element. This is, of course, a grouping facility however it is not the only one. It is possible to create a single entity which may include many elements. This entity is called array and it is possible to operate on its elements by using the array operators of the PostScript language. We give some details about these topics and some illustrative examples in the following lines by ordering the array operators.

[  :This creates a marktype element in the stack. Unless its companion, ] is given it plays the same role as the mark command. All elements entering the operand stack after this operator are considered individual elements although there is a marked reference point before them in the operand stack. The following session explains the relation between [ and mark

GS>[ pstack
-marktype
GS<1>2 3 mark pstack
-marktype-
3
2
-marktype-
GS<4>

]  :This is the companion of the above operator [. The stack must contain a marktype element before this element is given. In fact, this is considered as the ending mark for the array and completes the array construction. Since each ending needs a beginning, PostScript seeks its companion, the beginning mark ] when this element entered the operand stack. If [ is missing then PostScript returns an error message and the desired action is not done. If this element enters the operand stack immediately after [ then an empty array is created and stored as a single entity in the operand stack (the existing marktype element turns out to be a part of the array, hence it does appear as a different element) as the following session shows:

GS>[ pstack
-marktype-
GS<1>] pstack
[]
GS<1>

After all this, the operand stack contains only a single element, an empty array. A non-empty array can be directly created by using [ and ] together with the array elements at the same time in a single entrance to the interpreter as the following session shows.

GS>[ 1 2 3 ] pstack
[1 2 3]
GS<1>

As we can see the array is considered just as a single entity.

We can create an array with a specified number of elements even we do not want to specify each element. This can be done by using null element which means nothing as follows.

GS>[ null null ] pstack
[null null]
GS<1>

array:This command needs an integer parameter. If the parameter is n then the command is given as n array. When done, this creates an array which contains exactly n null elements. It does the same thing with the beginning and ending marks for array creation. For example 3 array is the same as [ null null null ]. It seeks its parameter as the topmost element of the operand stack. If it is given before the command then it enters the operand stack and becomes topmost element which is taken by the command. If the parameter is not given before the command then the topmost element of the operand stack determines what will happen. If it is an integer then its value is used by the command, otherwise an error message is displayed due to incompatibilites.

length:This operator evaluates the number of the elements in an array. The null elements are also taken into evaluation. The operator needs a parameter which must be an array. The operator takes its parameter as the topmost element in the operand stack. The parameter disappears from the operand stack after the execution of the operation. Hence, if the parameter is given before the command and it is an array then everything goes well and a number is located as the topmost element of the operand stack. For example

GS>[ 1 2 3 4 5 ] length pstack
5
GS<1>

If we do not give the parameter for the length operator then either the topmost element is used by the command and removed from the operand stack and the number of the elements in the array is located as the topmost element of the operand stack

GS<1>pstack
[1 2 3 6 7]
GS<1>length pstack
5
GS<1>

or the existing topmost element of the operand stack is not an array hence an error message is issued.

get:This operator needs two parameters. The first and second parameters are respectively an array and an integer. Operator gets a specified element from the array. The position of the element to be gotten is the second parameter of the operator. The positions are indexed with natural numbers, that is, they start from zero. In fact those rules apply to all operator parameters. The parameters are used by the command and are removed from the operand stack. Their types must compatible with the given values. We do not emphasize on this point anymore. The use of get is as follows.

GS[1 6 3 0 9] 0 get pstack
1
GS<1>

put:This command needs three parameters. They are respectively array, index, and the element to be inserted into the array. When issued, the command gets the array given by the first parameter, finds the location whose index is specified by the second parameter and replaces the array element at that position with the thing given by the third parameter of the command. The resulting array, however, does not get stored in the operand stack. Hence, for explicit use of put operator, we can define an array variable (or a key in PostScript terminology). The application is done on this variable and the result is then entered the operand stack and displayed from there. Look at the following

GS>[1 2 3] 0 8 put
GS>

Nothing happens here, inside the operand stack. But there is no error message as well. In fact, put does what it must do, but result is not stored into operand stack. To get the result of the same action in the operand stack we can follow the below session.

GS>/ar [ 1 2 3 ] def
GS>ar 0 8 put
GS>ar pstack
[8 2 3]
GS<1>

Here first an array variable (in plain English) or a key (PostScript terminology) is defined. The variable name is ar. The second step is toward the changing of the first element (with zero index) with 8 by using the put operator (command). After this ar pstack inserts the value of the array variable ar into the operand stack and displays its content. We shall mention about the variable definition later in this article. We also mention about the dictionaries and dictionary stack in the future articles of this series.

getinterval:This operator creates a subarray from a given array. It needs three parameters. They are respectively the array, from which the subarray will be created, the index which specifies the first element of the subarray, an integer which specifies the number of the elements of the subarray. When issued, command takes a number of elements (given by the third parameter) from the array (given as first parameter) starting from the location (given by the second parameter) and copies them into a new array. The new array is inserted into the operand stack. For example,

GS>[1 2 3 4 5 6 7 8 9] 2 3 getinterval pstack
[3 4 5]
GS<1>

putinterval:This replaces a subarray of a given array with a given other array. Three parameters are needed: First the array whose subarray will be changed, second the integer which denotes the position of the subarray to be changed, and third the subarray to be replaced with the subarray of the given array. The command is very similar to put. It does not put the result into the operand stack. To see how the result is displayed look at the following session.

GS>/ar [1 2 3 4 5 6 7 8 9] def
GS>ar 3 [0 0 0] putinterval
GS>ar pstack
[1 2 3 0 0 0 7 8 9]
GS<1>

aload:This takes an array as parameter and copies its elements as single entities into the operand stack. The topmost element of the operand stack after the execution is the array. That is,

[1 2 3] aload pstack
[1 2 3]
3
2
1
GS<4>

astore:This command replaces all of the elements of an array given as the second parameter with a sequence of elements whose number is equal to the length of the array from the operand stack. The result is a new array with those replaced elements.

GS>1 2 3 [null null null] astore pstack
[1 2 3]
GS<1>

copy:This copies the first parameter which must be an array into the first subarray of the second parameter which must be an array also. The displayed result is the subarray copied not the second array. To see the last form of the second array the variable definition can be used as follows.

GS>[1 2 3] [4 5 6 7 8] copy pstack
[1 2 3]
GS<1>/ar [4 5 6 7 8] def
GS<1>[1 2 3] ar copy
GS<2>ar pstack
[1 2 3 7 8]
[1 2 3]
[1 2 3]
GS<3>

The array elements need not to be integers. They may be strings an arrays as well. This means that the nested structure for arrays is permitted in PostScript. This advantage enables us to use matrix operations and macro defintions on matrices. Even it is possible to deal with the tensors or multidimensional sequences in principles. For the moment we suffice with this information.

Keys and Variables

It is possible to define variables in all programming languages. To use variables facilitates to deal with the quantities without considering their locations in the memory unit. You can get the value stored in a segment of memory unit either by giving its adress or giving a key for its content. The first approach is the use of the pointers like in C ... If you do not want to be involved with the adresses then the use of key is sufficient for you. However, the compiler or the interpreter of the language must take care of the memory access and other operations in this case. For this you define just a word or string, a name in plain English, and then assign a value for this entity. All these actions are, in fact, to say to the programming language what the variable is and what its value is from your point of view. The compiler or interpreter specifies a memory segment for your variable and all assignments go to this segment as data. A similar structure is also available in PostScript. The PostScript has dictionaries which contains names or keys and related definitions. In PostScript terminology, a dictionary is composed of pairs whose first element is called key and the second has the name value. For example, add is a name (key) which makes arithmetical addition (value). PostScript knows the meaning of add because it is stored in a dictionary which ic called systemdict. If you give 1 2 add pstack command you see the result 3 because PostScript looks these three names and does the following. It finds 1 and 2 and then add. The first two objects are integers so they are stored to the operand stack. The third object is a string which may be a name (key) or not. PostScript looks at its dictionary(ies) to find this name. If found, the action defined in the dictionary(ies) takes place. Since add exists in the system dictionary, systemdict there is an action (value) for this name (key). It is respectively, the popping of two topmost elements from the operand stack, to evaluate their arithmetical sum, and then the pushing of the resulting value into the operand stack as the topmost element. The remaining part of the command is the string pstack which exists in the system dictionary and means "display the current content of the operand stack to the standard output" so its action takes place. On the other hand we could give the following line to the interpreter by mistake or on purpose: 1 2 dad pstack. If so then the interpreter would broadcast an error message due to the fact that there is no such key or name, dad defined in the dictionaries of the PostScript.

We are not limited by the existing definitions of the system dictionary of the PostScript. It is possible to define some procedures or identifications as actions of the user defined commands. If the definition is an identification then we call the name or key as variable although it is not used in PostScript terminology. Our purpose is to make recalls from the other well-known programming languages. For variable definition all we have to do is to issue /x value def where value is an object of PostScript like integer, array, string ... For example we may consider the case of entering /x 12 def at the interpreter prompt. When done, the PostScript interpreter takes three objects, /x, 12 and def. The objects starting with a slash character are recognized as the keys or names. They can be inserted into the operand stack without regarding whether they are existing in a dictionary. def command exists as a key-value pair in the system dictionary of the PostScript and needs two parameters, first the key or name which will be defined, second the value which will be assigned to this key. Hence, PostScript creates a key-value pair after this command, /x 12 def, is issued and puts it into a specific default dictionary which is called current dictionary. It is the topmost element of the dictionary stack we shall mention later in this series in detail. After this point, x will be recognized by PostScript as 12 during the entire session.

In principle, any string starting with slash character can be used as a key value. However it is better to avoid using the characters other than letters and numbers. Otherwise, the characters like punctuation marks, slash, etc. may create some undesired actions in the use of the resulted key because they may have special roles in PostScript like slash caharacter. The limitations on the number of the characters in the string given as a keyname comes from the capacities and limitations of the interpreter you use. In fact, it is not pleasent to deal with a hundred character long keyname although it may be possible to use it. The PostScript is case sensitive so it brings a lot of flexibility. The names must not be chosen from the system keys of PostScript. Otherwise your key definition overwrite the system command which is not pleasent. For example, if you issue /add 13 def then add turns out to be just a constant and the addition capability of the PostScript becomes paralyzed during the remaining part of the session. There may be more things to say about this topic but we will suffice with these paragraphs leaving the remainder to the future articles.

Loops

PostScript has repetitive purpose constructions, namely loops. They provide the way of doing a number of same type executions. The number of the executions may be thousands, millions, billions or zillions. It does not matter. In just a single command, entire procedure can be executed by using loops. We tell about loops and loop commands in the following lines.

repeat:This command needs two parameters, the first of which is just an integer and denotes the number of the repetitions while the second one is generally a procedure which is a block of the actions. In PostScript a block is defined by the delimiters { and }. The instructions or the commands can be consecutively put between these delimiters. The syntax of the command is like n { ... } repeat. When given, PostScript takes the first parameter and inserts to the operand stack. Then the procedure given by the block { ... } is taking and resolved to know about its action. Finally repeat is searched and found in the system dictionary and its action is taken place. Since the defined action is just a repetition, the procedure given as the second parameter is executed n times. To be more specific we can give the following example.

GS>3 {1} repeat
1
1
1
GS<3>

Three integer values, 1 are inserted into the operand stack by this command. In fact, the procedure is very simple here. It is just entering 1. We can give another example which uses a little bit more complicated procedure as follows.

GS>1 5 {1 add} repeat pstack
6
GS<1>

In this example, first 1 enters the operand stack then the procedure {1 add} is executed 5 times on the operand stack. This 5 step execution proceeds as follows. In the first step 1 add is executed. add needs to parameters, the second of which is given in the procedure. The first parameter (or operand) in PostScript terminology) is taken from the operand stack. Therefore in the first step of repeat, 1 1 add is executed. The only element of the operand stack, 1 is deleted after this execution and the result which is 2 is inserted as the topmost element. Then the second step continues with 2 1 add and results in a one-element operand stack whose only element is 3. This leads the execution of third step operation 3 1 add. The remaining two steps involve the execution of 4 1 add and 5 1 add. Therefore the operand stack has the only element 6 after everything is done.

for:This command uses an integer control variable for the repetitive execution of a given procedure. The control variable starts from a given initial value and increases its value after each individual execution of the procedure. This action continues until a prescribed limit value is exceeded. Therefore command needs four operands, first three of which are respectively Initial, Increment, and Limit. All these three parameters are expected to be numerical values, integers or decimal numbers. The fourth operand is the procedure which may be either a single command or a block of commands embraced by { and } respectively. The full syntax of the command is Initial Increment Limit Procedure for. When the command is executed, PostScript creates a temporary counter variable (control variable in PostScript Terminology) and sets it to the value of Initial. This value is inserted into the operand stack. It may be or may not be used by the Procedure as an operand. If it is used then it is removed from the operand stack otherwise it is stored in the operand stack. After the counter variable is set to Initial the Procedure is executed. This is followed by increasing the counter variable by the value of Increment. Then the cycle continues in this fashion until the new increased value of the counter variable exceeds the value of Limit. The exceeding means to go out of the interval between Initial and Limit. If Increment is positive then the execution of for is completed when the counter variable becomes greater then Limit. Otherwise, for execution stops when the counter variable becomes smaller than Limit. The range or the interval of the counter variable must be non-empty. This means that Initial must be smaller than Limit when Increment is positive and vice versa. To be more explicit we can give the following session as an example.

GS>1 -0.5 -1 {} for pstack
-1.0
-0.5
0.0
0.5
1.0
GS<5>clear 0 1 1 23 {add} for pstack
276
GS<1>

The first command here does nothing because there is no procedure to be used, it is null. Hence all the values of the counter variable are stored in the operand stock and remains there since there is no procedure to use these values. The second command however includes add procedure which needs two operands. The first operand is always the topmost element of the operand stack here and the second operand is the value of the counter variable of the loop, which is inserted to the operand stack at that step. The second command evaluates the sum of the first 23 positive integers. It needs an outer initial value for the addition procedure. It is given as 0 before the command. In other words, 0 is not a part of the for command.

forall:This commands executes a procedure for each element of a given array. For this purpose, it enumerates each element of the given array starting from zero. It uses a temporary counter variable to control the loop. The initial value of the counter variable is 0, its increment is 1 and its limit is the length of the given array. The loop scheme is almost same as the one of the for command. The only difference is the employment of the array elements instead of counter variable by the procedure of this command. The command needs two operands, the first of which is the array whose elements each by each will be used by the procedure. The second operand is the procedure. The complete syntax of the command is Array Procedure forall . The following PostScript session which aims the evaluation of the sum of a given array and the pushing all elements of another array into the operand stack is presented for further explanation of the command.

GS>0 [11 23 45 -89 26 12 0 -34] {add} forall pstack
-6
GS<1>[1 22 -12 0] {} forall pstack
0
-12
22
1
-6
GS<5>

loop:This command seeks only one operand which is the procedure to be executed. When issued, it repeatedly executes the procedure without stopping. In other words, it is an infinite cycle which can be broken only via an external interrupt like Ctrl-C if the procedure does not have a special structure. If the procedure to be executed has either exit or stop command at somewhere then the loop cycling is stopped when one of these commands is encountered and the control returns to the next object of the interpretation. The syntax of the command is Procedure loop.

Procedure or Macro Definitions

In PostScript, Procedure or Macro means an ordered set of objects. These objects must be collected inside of a pair of block delimiters { and }. They can be named by using key definition like /Macro1 {1 add 2 mul} def. If done, then the key /Macro and its value {1 add 2 mul} is added to the current dictionary which is located into dictionary stack as topmost element, as a key-value pair. Then, when the object Macro1 is given at the interpreter prompt, its action takes place. The procedure defined in the block may be complicated or simple as much as you wish. In future articles we emphasize more on macros. For the moment this introductory information suffices for our purpose.

Exercises

Starting from this article we will give some exercises for the reader. The answers of the questions here will be given in the following article.

  • 1)  Write a procedure which takes an integer operand and evaluates the sum of the squares of the integers between 1 and this operand inclusive.

  • 2)  Write a procedure which takes two integer operands and calculates the cubes of the integers between the first and second operands inclusive. The procedure must create an array and put these cubes into the array as elements.

  • 3)  Write a procedure which takes an array operand and calculates the arithmetic mean of the elements in the array. It must also evaluate the square root of the sum of the squares of the array elements.

  • 4)  Assume that the PostScript does not have exp procedure and write a procedure which takes two numerical values as operands and considers the first operand as base and the second operand as the exponent. The procedure will evaluate the exponent-th power of the base. The operands must be maintained in the operand stack after the operation.

  • 5)  The mathematical object, matrix, can be considered as the array of arrays. An N row square matrix can be represented by an N-element array whose elements are again N-element arrays. Call this type array of arrays "Square Matrix". Write a procedure which takes a square matrix operand and evaluates its trace (the sum of the diagonal elements). The result and the original square matrix must be maintained after the operation.

  • 6)  Write a procedure which takes a square matrix operand and evaluates its transpose (the rows are interchanged by columns). The result and the original square matrix must be maintained after the operation.

  • 7)  Write a procedure which takes two square matrix operands and evaluates the matrix sum of them. The results and the original square matrix must be maintained after the operation.

  • 8)  Write a procedure which takes two square matrix operands and evaluates the matrix product of them. The results and the original square matrix must be maintained after the operation.