PingPong Tutorial

Abstract

This file shows some simple programming constructs in PingPong and advances to more complex programming concepts. Be sure to read the Language Reference for the specification of the mentioned instructions.

General

For now it is not necessary to completely understand this section. You will understand it later.

PingPong interprets programs written in postfix notation. All instructions (see Instruction Reference) are the same size of one character and operate on a stack. All characters which are not instructions are pushed to this stack after they are converted to the PingPong Character Code. PingPong is a two dimensional language (see Control Flow) which operates on a matrix (called PingPong-Space) containing code and data. Code and data are treated equivalently which makes self modifying code possible. The matrix contains 32-bit signed integer cells and the stack contains 64-bit signed integer cells. All cells can be interpreted as characters or as numbers depending on the operation.

In an extension to the language it is possible to create and use multiple PingPong-Spaces. This is optional and not necessary for solving simple algorithms but it introduces object oriented concepts into the language (see Object Oriented Concepts).

Arithmetics

Example:

34-

Pushes 3 to the stack, then it pushes 4 and it applies the "-" operator to the two topmost values on the stack. This operator pops 4 and 3, calculates 3-4 and pushes the result. So, after executing this piece of code, the stack contains -1.

The common operators are:

+Add
-Subtract
*Multiply
%Divide and calculate remainder
&Bitwise AND
~1's complement

Note that
52%
Pushes 2 (the result of division) and 1 (the result of modulo) to the stack.

I/O

Example:

;;+: ,1+.@

The two ";"s in the beginning read two numbers from the input stream. For each number, one line of the stream is read and converted into an integer. So, after the second ";" we have the two entered numbers on the stack. ":" pops the topmost value and prints it to the standard output stream (without newline).
The following instruction (",") just reads a single character from the input stream and pushes its character code to the stack. Then, 1 is added to the character code and finally "." prints the resulting character to the standard output stream.
The "@" instruction terminates the program.

Example:

H.e.l.l.o.$ .dlroW.....@

This piece of code prints out the string "Hello World": Push "H", print "H", push "e", print "e" and so on. The "$" instruction is called "escape instruction" and forces to push the next character, even if it is an instruction (space would be nOp otherwise).
Note that you have to push a string in the reverse order if you want to seperate printing and pushing, like it is done for the word "World" here.
See Hello World - Improved for a smaller implementation.

Using API functions it is possible to redirect input and/or output to other devices such as TCP/IP connections or files. See API Reference.

Note that characters are not converted to ASCII codes. PingPong uses an own character code table, the PingPong Character Code Table.

For advanced I/O there exists another type of streams, the binary streams. Here the data is not interpreted as characters but as bytes or as 64-bit integers (depending on the operation). See the I/O-Section of the Language Reference for a more detailed explanation.

Control Flow

Example:

#/ \
 \ /

Is a infinite loop. The "#" instruction causes the interpreter to skip the next character ("/"). So the next instruction is a space (" ") which does nothing (nOp). The next instruction ("\") changes the direction of execution. Just imagine the slashes and backslashes as PingPong rackets and the instruction pointer as the PingPong ball. You can now easily see that the instruction pointer will go around this loop forever.

Example:

#||

Is another infinite loop. Pipe ("|") and underscore ("_") are also reflecting characters. When "|" is hit while moving horizontally, the execution direction is reversed. When traversed vertically it does nothing. The opposite applies to "_".

The empty program is a infinite loop, too. This is because the execution wraps around the edge of your program. So if the instruction pointer leaves the program to the right it comes in again in the same line from the left (equivalent for all other directions).

Skip Instructions

Example:

12#8-

The "#" instruction skips the next instruction (in this case the "8" is not pushed) and therefore the stack would contain -1 (= 1 - 2) after executing this.

PingPong supports conditional skips which can be used to create "if" instructions and "while" loops.

Instructions:

#Skips always
>Skips if top value on the stack is greater than zero
<Skips if top value on the stack is less than zero
=Skips if top value on the stack is equal to zero

Note that conditional skips do not pop any value, they just examine the topmost value on the stack.

Example:

;>@H.e.l.l.o.

Reads a number from the input stream. If the number is greater that zero, the "@" instruction is skipped and "Hello" is printed out. Otherwise, the "@" would terminate the program without printing anything.

Example:

#/;=\@
 \ °/

This is a loop which is entered by "#" and requests the user to enter a number. If the number is equal to zero ("="), it leaves the loop by skipping "\". If the number is not zero, the "°" instruction is executed, popping the topmost value from the stack and discarding it.

Example:

;=\Z.e.r.o.@
  \:       /

This code prints "Zero" if the entered number is zero, or otherwise it prints the number.

Stack Manipulation

Instructions:

°Pop one value from the stack and discard it
"Dublicate the topmost value on the stack
^Exchange the two topmost values on the stack

Note that popping from the empty stack always returns zero, so you won't have an underflow of any kind. This is also important for the stack manipulation instructions except "°". """, for example, pops one value and pushes it twice, so that the initially empty stack contains two zeros after this instruction.

Hello World - Improved

1-dlroW$ olleH#|.# <|@
and
1-dlroW$ olleH#/<\@
               \./

do essentially the same. Just the loop is constructed in a different way. You noticed the "1-" in the beginning. This calculates (0 - 1) if you remember that popping from the empty stack always returns 0. In PingPong by convention, strings are terminated by a negative number because the number 0 is also the character "0". So if you'd end a string with 0, no "0" character would be possible.
Additionally all API taking string arguments expect strings terminated by a negative value.

If you'd like to print a "newline" you'd have to take a look at the PingPong Character Code Table. You would find out that ASCII code 10 is PPCCT code 138. 138=13*10+8. Therefore your code would be:

AD*8+.

If you were clever, you would notice that 23*6=138 too, so the following code does the same thing:

6N*.
(Highly optimized PingPong)

Threads

Example:

{\H.e.l.l.o.$ .}
 \W.o.r.l.d.@

Is a multithreaded "Hello World" program. The instruction "{" forks the current thread and creates a new thread having absolutely the same state as the first one (copying the stacks, etc).
"{" works like a skip for the current thread while the new thread starts immediately at the following instruction. To terminate the current thread, use "}" and use "@" to halt the whole program. Both instructions produce an exit code which is the top value on the stack - but you can ignore that.
In this example the output order of the characters is non-deterministic and so is the question whether "@" or "}" is reached first. This depends on the thread switching behavior of the host system.

Advanced Programming Concepts

The Help Stack®

For some reasons you will need to access values being not on top of the stack, but somewhere below, without losing the top values. PingPong provides another stack, the so called Help Stack®, and two instructions to move values from the ordinary stack to the Help Stack® ("`") and back ("'"). So, you could move some stack values to the Help Stack®, do the desired operations on the values below and then move the top values back. With tricky use of the "`", "'" and "^" (exchange) instructions you can also move values up and down in the stack.
Maybe the most important thing about the Help Stack® is that you can use those two stacks like a list with sequential access (using "'" and "`" to move the access-pointer).

Instructions:

`Move a value from the stack to the HelpStack
'Move a value from the HelpStack to the stack

Turing Completeness:

You can simulate a list with sequential access using the stack and the Help Stack®. Additionally you can partition the PingPong-Space into some regions you could call "states". You may say that your program is a state-machine, which is in a particular state when the instruction pointer is in the appropriate region. Using conditional jumps and control flow operations your instruction pointer would be directed to other regions, making state transitions. You could load a list into the stacks in the beginning of your program and print it in the end. Sounds familiar? Bingo! You've just created a Turing-Machine simulator in PingPong which shows that PingPong is Turing-Complete even without taking advantage of the ability to access the PingPong-Space.
Maybe someone could make a formal proof and send it to me?

Method Calls

Example:

100[$ .200[@
H.e.l.l.o.]
W.o.r.l.d.]

Could that be another "Hello World" program? Yes, it is. It introduces method (procedure, function or subroutine) calls to PingPong. Actually the second and the third line are subroutines here, printing either "Hello" or "World". The first line calls these routines and prints a space between the words.
The "[" in the first line is the call instruction: It pops three values from the stack, defining the target of the call, then it pushes the current instruction pointer and jumps to the target. The three popped values (in the order they are popped) are the PingPong-Space-ID, the X coordinate and the Y coordinate of the target. If you just use one PingPong-Space, as we are doing here, or if you jump to a position in the current space, the PingPong-Space-ID should be zero.
With this knowledge we can go through the first line of code now: "1" pushes the Y coordinate of the subroutine (this is the second line), then "0" is the X coordinate (the first column) and finally the Space-ID "0" to stay in the current PingPong-Space. "[" does the actual call, resuming execution at the first column in the second line without changing the direction of the execution.

So, how do we return from subroutine calls? Furtunately the "[" instruction pushes the instruction pointer before the jump - that's the location of the "[" itself. This pointer can now be used in a jump-instruction "]" to return to the calling position. Note that "jump" does not execute the target instruction, while "call" does so. This behavior is necessary because otherwise "jump" would execute the calling instruction again. It is also important to remember that when you use the "jump" for other things than returns from subroutines, so always jump to the character before the one you would like to execute.

Passing values to subroutines and back again:

;100[:@
```2*''']
or
100;`[':@
'2*`]

Here the second lines of both examples are subroutines calculating the input value times two. When entering the subroutine in example one, the input value lies below the return address, and we have to move the return address (three components) to the Help Stack®. Before returning we have to move it back, of course.
The second subroutine takes its arguments on the Help Stack® and also places the return value there. It's a matter of taste which approach you prefer.

Contexts

Maybe you will need to store local (static) variables in your subroutine, or you want to call further subroutines. This is no problem as long as you know exactly where your method is located in PingPong-Space, but is becomes a problem if this method was loaded into the memory at runtime to an arbitrary position. Using contexts, you will be able to do relative memory access for reading/writing and relative method calls or jumps.
Another feature of contexts is, that they resize the stacks to the heights at context creation. This can be used to clean up the stacks without knowing how many values to delete. But you should reserve stack-space before you create a new context if you want new values to remain on the stack, so that they aren't cleaned up afterwards.

See Context section of the Language Reference for further details.

Object Oriented Concepts

<not yet implemented>

PingPong is an object oriented language. Or in other words, it supports concepts which can be interpreted in such a way - with a lot of fantasy. PingPong is not limited to a single PingPong-Space - you can create lots of those cute little arrays with a special API call (see API). When executing code in a certain space, it is possible to jump to other spaces or to call subroutines there. But while executing in one space you can't access the data of another space. So you can see each PingPong-Space as instance of the "class" which was loaded when the space was created. "Class" corresponds to "file" here.

But how to do inheritance? - Simply create a PingPong-Space with the desired subclass and call the class' init method (however you code this). This init method has to "underride" its class by the superclass(es) and call their init method(s), and the superclasses "underride" themselves with their superclasses and so on. You should take care that your code does not mask (or override) the init methods of your superclasses. As you can imagine, "underriding" is the opposite of "overriding" and is used to merge the current code with the code of the file (or class) to load.
Underriding loads a file into the current space, but it does not overwrite existing code. Only positions with noP's are changed (noP = no Program, not to be confused with nOp = no Operation).
Overriding loads a file and replaces existing code but only on positions where there is no noP in the loaded file.
So how to place noP's in source files? - Generally the whole PingPong-Space is full of noP's. That's just where no file has been loaded yet and no data has been stored, so everything outside the bounds of your source file is noP. This also applies to blank lines, or in general to all positions before the first and after the last character in a line of the source code. But sometimes you will need to place noP's inmid of your code, for example to allow underriding in these areas. Do this by placing the character #160 (it's invisible, but maybe you can urge your editor to display it in another background color).

When your class overrides a method of a superclass, sometimes you will need to call the superclass' method within yours. This will become a problem if you have overwritten it completely. To make methods virtual (in the superclass), you could place another method call instruction at the entry point which calls the actual method (so, interface and code are separated :-). And when you override it, you just overwrite this method call by a call to your implementation. This implementation could now call the original method of the superclass. Brilliant, isn't it?

©2001, Michael Wurm. Legals