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.
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).
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.
+ | 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.
;;+: ,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.
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.
#/ \ \ /
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.
#||
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).
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.
# | 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.
;>@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.
#/;=\@ \ °/
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.
;=\Z.e.r.o.@ \: /
This code prints "Zero
" if the entered number is zero, or otherwise it prints the
number.
° | 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.
1-dlroW$ olleH#|.# <|@
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*.
{\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.
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).
` | Move a value from the stack to the HelpStack |
' | Move a value from the HelpStack to the stack |
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?
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.
;100[:@ ```2*''']
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.
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.
<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?