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.
Initially the PingPong-Space is filled with character code PPCCT #226 (ASCII #160), indicating that there is no program (noP). The interpreter reads the source file, converts all characters to the PingPong Character Code and writes them into the space starting at position (0,0). Each character fills one cell of the PingPong-Space (also the Tab) and all cells which are out of the bounds of the source file remain unchanged. The character ASCII #160 (noP) in the source file indicates that the PingPong-Space should not be altered at this position. This makes no difference here, but there are API functions to load additional files into the space at execution time. Here, the character ASCII #160 in the loaded file leaves the original data untouched, it's a kind of mask.
The loading mechanism described above is called "overloading" (or "overriding" in terms of OOP). There is a second type of loading files called "underloading" (or "underriding") described in the API-Reference.
It is assumed that the source code is ISO-8859-1 encoded.
The execution of a program starts at position (0,0), corresponding to the first character in the first line of the source code. The direction of execution is initially set to "left-to-right". The code is executed sequentially in the current direction.
Reflecting instruction ("|
", "_
", "/
", "\
")
change the direction of execution just like a PingPong ball would be reflected when
hitting one of those characters.
When the instruction pointer leaves PingPong-Space, it comes out on the opposite side again. So the PingPong-Space is something like a closed torus (it wraps around).
PingPong has two stacks: the default stack and the HelpStack®. When you push data or
perform operations, you always work with the default stack. There are two operations to
modify the Help Stack®: "`
" and "'
" are used to move a value from
the stack to the Help Stack® and back again. Moving from one stack to the other means popping
from the source stack and pushing the same value to the target stack.
The stack's sizes are only limited by the memory of the host system. If the memory goes low, the interpreter terminates. If you try to pop a value from an empty stack, you'll always get 0 without any underflow. Note that this means that you will not be able to detect if a stack is empty or not.
Binary streams are neither supported nor specified right now.
Character streams convert characters from ASCII to PPCCT and back, depending on the
operation. When reading, you can rely that each line break is converted to a single
line feed automatically. A line break may be a single line feed or a carriage return followed
by a line feed. Note that "CR LF CR <sth. else>
" is converted to
"LF CR <sth. else>
". When writing to a stream you should only use line feeds
to be platform compatible, but here no conversion is done.
Additionally, character streams have the ability to read and write decimal numbers which are
converted from/to strings automatically. The reading of a decimal number is implemented as
following: The Stream is scanned for the first non-whitespace character, then the following
characters are concatenated to a string until the next whitespace (space, tab). A
line break, the character PPCCT #226 or the end of the stream always terminates reading. Then the
string is converted into a 64-bit signed integer. The default value is zero if conversion fails,
or the string is empty.
Using parenthesis "(
", ")
" you can create and close contexts.
When a context is created the interpreter pushes the old context's data to its context
stack. Then a new context is initialized with the current instruction pointer and the
heights of the stack and the Help Stack®. From now on all memory accesses and
procedure calls will take addresses relative to the position where the current context
was created.
When closing a context, the next context is popped from the context stack, if it is not empty. In all cases this will set the heights of the stacks to their initial heights at context creation. This is done by discarding values to decrement the stacks' heights, or by pushing zeros to increment it.
Initially the program runs in a default context. This context's memory offsets and stack heights are zero.
PingPong supports threads. Threads can be created using "{
" - this will cause
the current thread to skip the following instruction while the newly created thread will
execute it.
All stacks (default stack, help stack and context stack), the current context, the
input/output streams, the allocated resources (see API) and the execution direction are
copied to the new thread.
"}
" will terminate the thread hitting this instruction. If this is the last
thread the whole program will terminate.
Each resource has a reference counter, counting the number of threads being able to access the resource. Forking a thread will increment the reference counter of all owned resources and terminating a thread will decrement it. If the counter reaches zero, the resource is closed automatically.
When terminating a program using "@
" the current thread will be stopped and
all other threads, if existing, are notified to stop as soon as possible. If not all
threads stop within a specific period of time the program shoots all remaining threads and
terminates abnormally - this can happen if some threads are stuck in blocking
io-operations.
Using mutexes and events threads can be synchronized (see API-reference)
0
" - "9
" correspond to the codes #0 - #9.A
" - "Z
" have the codes #10 - #35 (useful for hexadecimal
representation).The stacks and the PingPong-Space always contain PPCCT encoded characters and all
IO-operations take or return PPCCT codes.
When a text file is loaded into PingPong-Space or a character is read from a stream,
it is automatically converted to PPCCT (so you can write your source code in ASCII).
Also when writing characters to streams, they are converted to ASCII. So the only
situation you will notice PPCCT is when you calculate with character codes.
Using this character mapping, the character codes of numbers correspond with
their values, so no distinction has to be made between numbers and characters in
the source code. Also capital letters can be used to represent integer values between
10 and 35, for example.
All integer values which cannot be pushed directly can be created out of smaller,
pushable values. For example #138 (= line feed) can be pushed like this:
N6*
because "N
"=23 and 6*23=138.
code | pops | pushes | description |
---|---|---|---|
+ | b a | a+b | add |
- | b a | a-b | subtract |
* | b a | a×b | multiply |
% | b a | a÷b (a mod b) | divide and calculate remainder. is also defined for b = 0 |
& | b a | a and b | performs a bitwise "and" |
~ | a | ¬a | pushes the 1's complement of a |
° | a | pops one value and discards it | |
" | a | a a | dublicates the value on top of the stack |
^ | b a | b a | exchanges the two topmost values on the stack |
` | a | a(h) | moves a value from the standard-stack to the Help Stack® |
' | a(h) | a | moves a value from the Help Stack® to the standard-stack |
< | skips the next instruction if the top value is less than zero | ||
> | skips the next instruction if the top value is greater than zero | ||
= | skips the next instruction if the top value is exactly zero | ||
# | skips the next instruction | ||
$ | the next character will be pushed even if it is an instruction | ||
¤ | i | pops a value i from the stack and executes it like an instruction | |
, | c | reads the character c from the current input stream (c is converted to PPCCT). c is -1 is the stream is closed | |
; | a | reads the number a from the current input stream, entered in decimal digits terminated by return | |
. | c | writes the character c to the current output stream (c is converted to ASCII first) | |
: | a | writes the number a to the current output stream, converted to decimal digits without newline | |
? | x y | c | reads the value c from mem(x,y) |
! | c x y | writes the value c to mem(x,y) | |
[ | sp x y | ip.y ip.x ip.sp | pushes the instruction pointer and the current PingPong-Space and jumps to (x,y) in the space "sp". the target instruction will be executed. |
] | sp x y | jumps to (x,y) in the PingPong-Space "sp". the target instruction will not be executed (ip will move first). | |
( | creates a new context (see context) | ||
) | closes the current context, resetting the stacks to their initial size | ||
{ | creates a new thread which starts with the next instruction. the current thread skips the next instruction (see Threads). | ||
} | a | terminates the current thread. if this is the last thread, the program terminates with exit code a. note that 0 is popped when the stack is empty. | |
§ | f args | results | calls the API function with the number f. the args and results depend on the called function |
| | reflects the direction of execution horizontally; traversed vertically it acts as a nOp (see Control Flow) | ||
_ | reflects the direction of execution vertically; traversed horizontally it acts as a nOp | ||
/ | reflects the direction of execution like a PingPong racket | ||
\ | reflects the direction of execution like a PingPong racket | ||
@ | a | terminates the current program with exit code a. note that 0 is popped when the stack is empty. | |
SP | nOp (no Operation) | ||
NBSP | noP (no Program, PPCCT #226, ASCII #160). Does essentially the same as nOp but it indicates that there is no source code at this position. this is important for under- and overwriting (see API for loading source files). |
All other characters are instructions to push themselves.
API functions can be invoked using the "§
" instruction after pushing
the needed arguments and the function number.
Some API functions take strings as argument, this is indicated by an asterisk after
the parameter name. In PingPong strings are a sequence of characters terminated by
a negative value.
Some functions return handles or take them as arguments. Those handles are numbers, identifying resources for the current thread. Other threads are not able to access the resource (not even with that number). The only way to share resources between threads is to create them before forking, because forking makes an exact copy of a thread, including all open resources.
number | arguments | results | description |
---|---|---|---|
-1 | HND | closes the specified object for all threads and releases all threads blocking on it (if possible). | |
0 | HND | closes the specified object just for this thread, so that other threads still can use it. If all threads close an object this way, the object will be destroyed |
All io-functions take the access mode and a resource locator as argument and return
the handle to the opened resource or a negative error code. Arguments with asterisk
are strings terminated by a negative number.
The access modes are: 0 = none, 1 = read, 2 = write, 3 = read and write.
If the resource is opened for reading, the stdin of the current thread will be set
to the resource's input stream. The same applies to stdout and the output stream.
Use function 15 to explicitly set the stdin and stdout streams to a resource.
Resource number zero is always the console the program is running in.
number | arguments | results | description |
---|---|---|---|
1 | mode filename* | HND | Opens a file for reading/writing. |
2 | mode url* | HND | Opens the resource specified by an URL. |
4 | mode x y dir | HND | Opens a stream to access the PingPong-Space starting at (x, y) in the given direction. values for dir: 0=UP, 1=RIGHT, 2=DOWN, 3=LEFT |
8 | mode address* | HND | Opens a socket connection. The address must have the following format: "1.1.1.1:80". |
9 | port | HND | Creates a TCP-IP server to wait for incoming connections on the specified port (see the following instructions on how to use the server object). |
10 | mode ServerHND | HND | Accepts a single incoming connection from a TCP-IP server object. Returns a handle to the socket. |
11 | mode ServerHND | HND | Accepts multiple incoming connections from a TCP-IP server object. Each accepted connection creates a new thread starting at the following instructions. The newly created thread gets a handle to the socket. If the listening thread is interrupted will continue execution after skipping one instruction. |
15 | mode HND | Sets the default input and/or output streams (depends on mode) to the specified resource. |
For thread synchronization two methods are supported: Mutexes and Events. A mutex is
an object that can be owned by a single thread at a time, all other threads trying to
acquire the mutex will wait for the owning thread to release it or to terminate.
Then one of the waiting threads will take control over the mutex. A thread can acquire
the same mutex multiple times, but it has to release it the same number of times.
Events can be used to let a number of threads
wait for something to happen (nomen est omen). They have an internal state (triggered or
non triggered) which can be set, reset or pulsed (set and reset in one) by any thread.
In the moment the state switches to triggered, all threads waiting for the event will
be released. Using the single pulse you can also just release one waiting thread which
will not affect the trigger state of the event. The order of release is not specified.
Both, events and mutexes are represented by the same type of object (sync-object), the
real behaviour of the sync-object depends on the way you use it.
number | arguments | results | description |
---|---|---|---|
16 | HND | creates a sync-object which is not triggered and not owned by any thread. | |
17 | HND | creates a sync-object which is triggered and owned by the calling thread. | |
18 | HND | get mutex | |
19 | HND | release mutex | |
24 | HND | set event. releases all threads waiting for this event. further calls to wait will not block. | |
25 | HND | reset event. further calls to wait will block. | |
26 | HND | pulse event. combines set and reset atomically. | |
27 | HND | single pulse. releases only one thread waiting for the event. | |
28 | HND | wait for event | |
32 | time | sleeps the specified time in milliseconds. |
This API is used to load text files into the PingPong-Space. This is done in the same way
like the interpreter loads the program's source into the space and can therefore be used
to load either data or program code.
Underwriting loads a file into the current space, but it does not overwrite existing
code. It changes only positions with noP's.
Overwriting loads a file and replaces existing code but only on positions where there
is no noP in the loaded file.
number | arguments | results | description |
---|---|---|---|
48 | filename* x y | Overwrites the PingPong-Space with the textfile starting at the given position. All characters are converted to PPCCT. | |
49 | filename* x y | Underwrites the PingPong-Space with the textfile starting at the given position. All characters are converted to PPCCT. | |
50 | filename* | SpaceHND | Creates a new PingPong-Space, loads the file into it and returns a handle to the space. <not yet implemented> |