rpn User Manual
rpn is a stack based programming language. Data, functions and programs are pushed onto the current data stack. Functions are evaluated in Reverse Polish Notation hence the name rpn. The last element pushed to the stack has position 0. So counting on the data stack starts from 0. Whenever an element is pushed onto the stack the other elements position is increased by one.
Consider adding two 2 + 3 = 5 from an empty stack.
2 <Enter>
results in something like this.
size=1 depth=2 0: 2
Firstly size=1 displays the size of the
current data stack. depth displays
the program stack depth and can be looked at later.
3 <Enter>
size=2 depth=2 1: 2 0: 3
Reverse polish notation is the natural way computers evaluate.
For maths this means + is added to the data stack.
The order is important as the function reads the first two
arguments from the data stack. The data stack is used to pass
the arguments.
size=2 depth=2 1: 2 0: 3 + <Enter>
size=1 depth=2
0: 5
For this example I have assumed that + evaluated
itself on the stack. This programming language supports two
modes, evaluating an object relative to the current stack or
placing the object on the stack(deferred evaluation).
In deferred evaluation mode the stack would have looked like this.
size=3 depth=2 2: 2 1: 3 0: +
Each element in the stack is numbered from the most recent 0 onwards. To copy an element already in the stack to the front use & operator. This operator is useful because it allows you to reach into the stack.
size=3 depth=2 2: 3.141592 1: cat 0: 2 2 & <Enter>
size=4 depth=2 3: 3.141592 2: cat 1: 2 0: 3.141592
When entering input the interpreter determines the type and evaluates it relative to the current stack and evaluation mode.
Some functions do conversions eg dividing an integer can return a real.
A dictionary holds the names of functions. If an input is not a number or function it is interpreted as a string.
A string can be declared explicitly by quoting the text. The quotes can not be part of the string. eg
"A silly string" <Enter>
0: "A silly string"
While the quotes appear in the display they are not part of the string, but are for rpn to interpret as a string and not interpret the white space as meaning to separate the tokens.
To avoid interpretation text can be quoted
to force a string variable onto the stack.
eg "/" for the forward slash and
not division.
To enter a program, enclose input within { } .
For example { HelloWorld } is a program
containing a string. Evaluating it places the
HelloWorld string onto the stack.
Complex numbers are entered in cartesian form. eg
(1.,3.) <Enter> 0: (1.00000000000000000000,3.00000000000000000000)
For historical reasons the word functions has
a few different meanings.
For maths people a function
takes one or more values and
returns a value. Maths functions are easy to build in rpn.
eg
f(x) = x*x+1.
These are implemented with the program data structure. The
stack itself is used to pass the x value.dup
is short for duplicate so you get two x's on the stack.
{ dup * 1. + }
To input the function enter the following. There are many ways to do this, here is one.
{ #dup #* 1. #+ @rev }
If we want to evaluate f(1.5), f(2.5), f(3.5) on the stack here is a way to do it.
0: { dup * 1. + } 3: { dup * 1.00000000000000000000 + }
1.5 1 & 2: 3.25000000000000000000
2.5 2 & 1: 7.25000000000000000000
0: 13.25000000000000000000
3.5 3 &
Alternatively have the function as a local variable.
1: { dup * 1. + }
0: f
var
1.5 f &
2.5 f &
3.5 f &
Here is the same program written without dup
using the maths power operator instead. The order of
operations is important. When I was in grade 7 this
was the drill.
{ 2. ^ * 1. + }
Every calculator has degrees, radians and grads along with polar, spherical and radians coordinate system modes, what does rpn have?
All of rpn's angles are in radians. This is because they are the natural units from a computational perspective. To facilitate conversion between radians and degrees use r->d and d->r.
The coordinate system is rectangular however there are functions to go from polar to rectangular and back.
The building blocks are there so you can easily build a convenience function yourself. For example I would like a function that reads in polar numbers in degrees and can print out a complex number in polar degrees.
pr={ d->r p->c }
pc={ dup abs 1 & arg r->d }
5. 20. pr & - to enter polar in degrees,
pc & - to display in polar coordinates.
Pointers are the most crucial data types because they are the basis of the programming language - without them programs could not be written effectively.
A pointer is something which points to something else. If you have data it has to have a location. The location is the pointer. And this is why pointers are fundamental to computer science.
Variables are a type of pointer. Support for storing and retrieving variables is provided. eg
32 hat var
- creates a variable named
hat
which stores the value of 32.
hat & hat rcl hat ->
- evaluates hat
on the stack.
- recalls the variables
contents to the stack.
- recalls the actual object
as a pointer to the stack, if modified so is
the original.
A named variable is accessed through a string argument. Most types of pointers support named variables.
As can be seen from maths functions the
&
operator is very useful. & combines accessing
the variable and evaluating it on the stack.
Since a copy of the original variable is make the operator is in general non-destructive of the original variable.
What this means is that I expect &
to be the
most heavily used operator because its simple.
Its brother is rcl which accesses
the variables but does not evaluate.
rpn accesses variables through strings. Most other languages upon encountering a variable do a lookup. For example if I place "x" on the stack and a variable x exists evaluate x on the stack instead.
For rpn its not clear as to which possibility of the variable is meant. Do you mean "x" the string, a pointer to the variable x or a copy of the variable x.
To remove this confusion the user is responsible for how a string is interpreted. The interpreter is not to interpret a string as a variable, only an operator is to do that. The & operator says evaluate the variables contents to the stack. This improved the language because it made clear that operators are fully responsible for interpretation and need to overload string for variable interpretation. Some do and some don't.
[]
creates an index into the stack and ->[]
gets a pointer into the stack relative to the index.
A pointer that is accessed with an integer relative to some other pointer. In rpn the stack size keeps changing so indexing into the stack from the bottom is not easy because the stack is in constant change here. However counting from the top is another story.
Provided you do not corrupt above the index into a stack a indexed stack pointer will keep track of the position in the stack that was originally refered to.
For stability rpn guarantees that all pointers accessed will be valid or ignored.
With indexes operations that are out of range ignored.
For pointers to objects if you destroy the original object the pointer remains valid because the object is only truely destroyed when there are no other objects pointing to it (reference counting on all objects implemented).
When a session is saved the evaluated pointers are realized. The pointers themselves were not saved and are effectively destroyed. In their place the pointers data is written.
The pointer object -> is saved and restored across sessions. But when evaluated rpn does not keep track of the original's location. This may not be unique, rpn guarantees only valid references. An evaluated pointer will not be pointing to the same object between sessions.
An exception is the indexed pointer [] which was made to be saved across sessions because it indexes the local stack so its meaning is always clear.
These pointers always point to their original source. Even if this gets annihilated it still exists because a pointer is pointing to it. These are literally pointers to the original object, which always exists if it has a pointer to it.
Pointers allow the user to operate into the stack, and not just on the first elements. When evaluated a pointer evaluates to what its pointing at. So a pointer to an integer returns an integer. However if this is modified so is the original.
2: 849 1: 23 0: 34
2: 849 1: 23 0: 34 2 ->
3: 849 2: 23 1: 34 0: 849
3: 849 2: 23 1: 34 0: 849 1 +
3: 850 2: 23 1: 34 0: 850
Motivations for pointers comes from considering how to implement algorithms in rpn. For example adding two vectors on the stack.
5: 1698 4: 23 3: 34 2: 904 1: -494 0: 3
3 -> swap + drop 3 -> swap + drop 3 -> swap + drop
2: 2602 1: -471 0: 37
A weakness in the language is that the vectors references
were not constant and generally programmers should not have to deal with this.
eg consider the vector operation in C++.
v1[k] += v2[k] .
As a consequence of the above vectors pointing into the stack were implemented. Its another type of pointer. Instead of a pointer to the data, its a pointer to the position in the stack. However the position is not counted from the start but from the top of the data stack. As data is added this reference stays constant provided the user doesn't delete in the stack and invalidate the reference.
The beauty in this is that we can have indexes pointing into the data stack which generally do not change as the stack size changes over time. This is great for writing algorithms.
3 v1 var[]
creates a variable called v1 which indexes
into the current stack at position 3.
0 v1 &[] copies the value at that position to
the front of the stack.
2 v1 ->[] returns an object which is also a
pointer to the data at position v1 + 2 in the data stack.
Now the programmer can write a more traditional algorithm for adding the two vectors from the previous example.
0 v2 [] 3 v1 [] 0 v1 ->[] 0 v2 ->[] + drop 1 v1 ->[] 1 v2 ->[] + drop 2 v1 ->[] 2 v2 ->[] + drop 3 dropn
or
0 v [] 3 v ->[] 0 v ->[] + drop 4 v ->[] 1 v ->[] + drop 5 v ->[] 2 v ->[] + drop 3 dropn
rpn was designed so that you can not crash the program when accessing a pointer out of range. This is a robustness issue and design decisions were made to ensure that this never happens.
These could be viewed as pointers resulting from an ordering of operations.
In math a+b is equal to b+a because the order of addition is not important. In general the order determines which variable will store the result of the operation.
a b + would in general store the result in a's memory
location.
Conversions between rpninteger and rpnreal are supported.
eg
Dividing two integers returns a real if the
division is not exact.
Adding an integer and a real returns a real.
However rpn is order driven.
a b + doesn't necessarily equal
b a + because the result is stored
at the first arguments memory location, except when promotion occurs
where the new promoted data element is placed on the stack.
In general the first argument is modified and the second discarded in a binary operation. Except where this doesn't make sense as in a promotion.
Since rpn supports pointers a variable may not be assigned to as you expected. However if you are not using pointers this isn't an issue. Conversions were supported to make the language more friendly.
From a practical perspective be aware of what types you put on the
stack and you can use the ordering.
eg for integer and real addition put the real first and then
the integer before addition. This ensures the real is the same
variable as before. This is a feature of the rpn calculator.
There are explicit functions to querie an ojbects type and convert between the types. See isinteger , isreal , iscomplex , isstring , isprogram , integer , real , string .
There are two modes of evaluation: immediate evaluation and deferred evaluation. In immediate mode the function is immediately evaluated onto the stack. In deferred mode the object is pushed onto the stack. Strings and numbers are invariant(do not change) when evaluated, but functions and programs have both forms of evaluation.
Before in immediate evaluation mode:
1: hat dup
After:
2: hat 1: hat
Before in deferred evaluation mode:
1: hat
dup
After:
2: hat 1: dup
When in immediate mode there is
a command to temporarily defer function evaluation
by prefixing the command with a # character.
eg #dup places the dup function onto the stack.
The # command is good when building programs in
immediate mode.
Similarly in deferred mode there is a command to temporarily evaluate
a function by prefixing the command with the @ character.
The first object on the stack can be
explicitly evaluated using
the eval function.
To guarantee evaluation
use @eval.
The evaluation mode is persistent and is the users responsibility. Its a state, and a way of computing. You could evaluate directly or build a program to do the same job then evaluate it. So often the programmer wishes to switch between evaluation modes.
This is further needed because even when building a program the programmer can still evaluate on the stack - the distinction between the program and the stack is meaningless here as they are both the same, except the program is nested down one more layer.
@+ and @- were implemented to
meet the switching between modes by permanently setting
and un-setting the
evaluation mode respectively.
# - instant deferred evaluation
@ - immediate evaluation
" - interpret the word as a string
Place in front of command without whitespace between them.
Nodes are rpn's local file system. The were named nodes and not directories so that there would be no confusion between the file system of the local computer and rpn's file system.
One of the aims of the language was to support a tree structure where users variables and programs can be stored and manipulated. A node hierarchy was implemented which is a tree of nodes which in the language are implemented as programs.
Unix like commands are available for manipulating the node
tree. eg cp, pwd, pushd, popd, mv .
The root node / is the base node of the tree.
Since this is also the symbol for division its interpreted
as such when placed on the stack.
Directories are processed as strings so the string / has to
be placed on the stack to mean the root node and not the
division operator.
Entering "/ forces the string on the stack.
This problem does not accure for other paths - only the lone root node.
"/ <Enter> 0: /
Nodes are persistent and can store user variables so can be used to build environments. eg Collections of useful programs.
See homesave
to save the node hierachy. When rpn is
first run it looks for the file
rpnhome.txt in the directory
where rpn was run from. If it exists
the file is read and interpreted as if the
user had typed in the commands themselves.
This restores the state as before.
The exception being pointers which have their variables realizes(values written).
A program has its own data stack and variable stack. rpn also has a current node state.
Programs are the core data structure in the language. A program has its own data stack. It also has its own variable stack. Since a variable can also be a program, a program can form a hierarchy of nodes.
When a variable is created through
var it is stored in the current node.
The primary data stack is the current programs data stack. Functions are evaluated on the primary data stack. Nodes can form a hierarchy
When a variable is created
through var
it is stored in the current node.
To make a new node (or branch) , create an empty
program and make it a variable.
{ } d1 var
Programs can be edited.
Move into the program
and edit the stack directly.
Use pushd to edit programs.
1: { a var b var a b + b }
pushd
There are two ways to move about directories.
pushd popd cd When a program is entered it pushes itself on the program stack.
When a variable is searched for the top to the bottom of the program
stack is searched for the first matching variable name.
pushd, popd allow exact scoping of variables and
also cyclic paths. In contrast cd is always a tree(non-cyclic) with each
decedent possibly overloading its parents variables.
cd clears the program stack and
loads the variables in its path from home. If successful it guarantees
the program stack state and is useful for this alone.
Utility functions like ls,
pwd, tree help
navigate directories. cd can be
used with relative paths, as can mv and
cp.
The rpnhome.txt
file is loaded if it exists. The file stores rpn's directories.
The environment can be permanently saved and brought back
through save and load commands.
Firstly save overwrites an existing file with
the same name, storing in the same directory
where the executable was launched.
Further save saves from the current directory onward,
so if you are not in / that will not be saved.
By default rpnhome.txt is loaded on startup if it exists, preserving rpn's state between sessions. See the user defined functions section for a persistence solution.
On of the strangest things about this language is compilation and execution are not separate, because when you are writing a program you are still evaluating on the stack.
This may not appear exciting but is. For example local variables can be initialized, other functions/ programs called and results used. Many other languages such as Lisp can do this, I'm just new to it.
However in rpn the consequence of such flexibility is that the order of the program is reversed. If something is input in unevaluated form it needs to be reversed. You could of-course put it in the right way to begin with but this makes even the most trivial programming task a feat.
For these reasons rev and
prev were implemented
to reverse the stack and program respectively.
For example
Before:
{ @- a var b var a b + b @rev }
After:
{ a var b var a b + b }
Or if I forgot to do the reverse at the end. Before:
0 : { b + b a var b var a }
@prev
After
0: { a var b var a b + b }
The evaluation mode needed to be switched off
with @- so
that the functions would be placed
on the stack and not evaluated.
Since this is persistent the mode will need
to be switched back on with @+.
Passing variables to functions is done via the stack. Support for parameter passing through the stack exists in the language with the use of local variables.
Local are variables local to the program being evaluated and will not collide with global variables( variables which have been defined further up the tree ). The local variable hides the global variable.
Have the local variables initialized in reverse order to the stack. Consider the following program.
Input: a b c Output: ab ac bc
There are 3 input variables which would be harder to manage without named variables. The following builds a function to do this.
{
c var b var a var
a & b & *
a & c & *
b & c & *
}
Local variables can improve the look of the program.
Local variables are overloaded. Creating another variable with an existing name does not overwrite the existing variable, but hides it.
Even within the same node/program there can exist multiple variables with the same name. The most resent one will be in scope.
You can remove a variable with the rm command
which in a sense is the reverse of var.
Problem: Sum the numbers 1 to 6 for the answer of 21.
Create and initialize variables.
0 i var
0 s var
{ i & 5 <= }
{ s -> i -> ++ + drop }
for
For the result
ls to view the s variables contents,
which hold the sum.
Notice the use of the pointers. + changes the first argument which
is s. So s is updated with the new value.
Consider writing the loop function in a
more structured way. The -> of i can be
removed because
++ does the pointer access. The program
need only be evaluated returning the result to the
callers stack.
{
0 s var 0 i var
{ i & 5 <= }
{ s -> i ++ + drop }
for
s &
}
Compare with C++ code
{
int s = 0;
for ( int i = 1; i<=5; ++i )
s += i;
}
I have given C++ code so that you can compare the code written in rpn and C++. rpn is not as conscise as C++. Unlike C++ in rpn you have to be aware that you are programming on the stack but they are different environments. rpn is not as easy to read, but the intent can easily be seen with a slightly longer look at the source code.
With pointers more strange code emerges. The named variables can be eliminated with the variables being directly on the stack. The catch is that you have to be sure you are pointing to the right variable because the stack size changes.
1: 0 0: 0
{ 1 -> 5 <= } { 0 -> 2 -> ++ + drop } for
0: holds the sum and
1: the counter.
The for loop consumes the programs. When the test evaluates it evaluates itself on the stack, leaving an integer. If that is 0 if failed the test else passed. The for program consumes the integer leaving the stack with the reference state.
The body evaluates itself on the stack grabbing a pointer to the sum, then a pointer to the counter. Notice that it had to adjust because the program itself put an integer on the stack so its reference increased by one. ...
1: 6 0: 21
Traditionally people move away from assembler or directly passing information using the stack because its harder to recognize what the code is. However I believe that the last example is very readable and simpler than with named variables. he success of the code was the use of pointers by rpn. I have not seen any other language do this.
Accessing variables on the stack is quicker than accessing named variables because strings and dictionary lookup are minimized. Operating in defered evaluation mode still compiles so writing code resolves or interprets the code as a list of types. Thus in general all the code is compiled, despite rpn being an interpretive language.
The for loop was difficult because
if the test statement never returned false
the loop would go forever.
This is most often a programmers mistake eg forgetting to increment the counter. Therefore a counter was put into the for loop to terminate after about 1000000 iterations with an error message(see rpnfunc.cpp to edit this number).
Programs and nodes are the same and
are evaluated on the same stack. So what
happens when you mismatch pushd
and popd?
The worst case is when
you can find yourself not in a node
but a program. Your location does not appear
right, the stack is different. Call
popd to restore the stack.
Another situation is when pushd
fails because the directory does not exist.
The command does not say if it worked or not
it just does or does not do the job.
rpn is not a strict language.
A strict language may return the status result after
each function call, which is then tested or discarded.
This is not what I wanted the language
to do. The idea is to write the code so it works.
If it does strange things fix it.
So if pushing the node/directory fails poping
it will if you are not in the root node,
because popd is equivalent
to .. cd.
So you will move up one directory.
Since pushd and popd
are not unique in behaviour they do not have
to be matched. For example
g1 cd ..... popd g1 pushd ..... popd g1 pushd ..... .. cd
The difference between cd
and pushd is that the
cd adjusts the stack up or
down relative to the path.
pushd adds a path to the stack
independent of the path.
You could use this to change a variables scope.
eg - assuming successful command evaluation
../../d4 cd the stack depth
is decreased by one because minus two directories
and then plus one.
../../d4 pushd increased the stack
depth by one.
pushd can be used to change
the scope. For example if a variable
had been overloaded (in scope) you could
pushd to the variables directory and
it would be in scope again.
You can manage the program stack by watching the stack depth count and current node/directory.
This is the hardest part of the rpn language with the question how do you interpret a program?
You read it and follow the commands? Since rpn places programs on the stack, in default mode it evaluates a program on the the callers data stack by fixing that stack reference and then going along each command in the program and evaluating it on the stack.
While this is generally a good thing, when you wish to change the directory thereby changing the location and hence the data stack is still where you called the program from garbage results.
eg
Let /d3 be a node and the current
node is /.
{ d3 cd h1 } evaluated on the
stack changes to /d3 but places h1
in / and not /d3.
If I had entered the same commands from the keyboard
h1 would be placed in /d3 's data
stack.
There are a few issues here. Firstly without a radical design change there is no one solution to the problem. rpn evaluated like this to support named local variables. However wysiwig or the need to have a programming language where what you keyed in exactly by hand could be put in a program with the exact same output was a key requirement, especially as the language doesn't have debugging tools it relies on this technique. There is no one solution.
So after much thought(going mad) I identified the two different ways that a program can be evaluated.
Each program was given a state field which is an integer representing switches that control how evaluation ocurres. So I do not have to decide how to evaluate a program, you do.
A default setting for programs of 3 where both bits are on/high was implemented, the same evaluation that gave the garbage result is the default. Here the program has its own local variables. If bit0 - the pushing of the program onto the stack is turned off you can not access local variables because the programs scope does not exist. Instead the calling programs scope will be used.
For that magical code where what you entered is eactly what you get set the program state to 0.
All this is controlled through one function p@ . One of the calls queries and the other sets the state.
States 0 and 3 are the main states with 0 being the most primative form of evaluation - it just reads and excecutes the commands in sequence.
When you can tailor something it feels comfortable. dictadd
allows the user to add their own functions to the dictionary. This is
really bootstrapping because rather than write functions in C++ and compile
them into the system you can use rpn instead.
For example I have a homesave function to save my
home directory. If it wasn't this simple it would have been
implemented as a C++ rpnfunction.
homesave={ dec "/" pushd rpnhome.txt save popd }
dec makes sure that integers are written in
base 10. Then move into the root node and write
the nodes/directories, returning to where you called
the function from.
So I recommend implementing minimal functionality in the C++ code and implementing the rest in rpn's language.
Managing user-defined functions also means that you may wish to delete
them. User-defined functions are stored in /bin.
When load loads a file it looks for variables in
/bin and initializes them as user defined functions.
dictadd adds variables to the /bin directory
to permanently store the functions.
If you wish to remove a user defined function it has to
be deleted from the bin directory. To do this use
rm .
However because a user-defined function doesn't let you access its
name (it evaluates the function or pushes it to the stack),
the name has to be forced onto the sack without evaluation.
"name forces
a string called name to be placed on the stack. Then
use rm from /bin
to remove the variable.
Finally homesave to save the current state, exit
and restart the program to be free of the definition, or call
load to re-initialize the interpreter. In general I just
remove it, when the program is quit the function naturally
dies.
rpn can be extended at the source code level. The source code can be modified any way you like. However as this is an application language support for user defined functions at the source code level was implemented.
By deriving from rpnfunction clients can extend rpn. Follow the conventions of other
rpn functions eg do not delete but dec() data on the stack.
Once a function is defined it needs to be added
to the interpreters dictionary. After the interpreter is initialized call
addtodictionary< T >(); in
rawinterpreter.h where
T is the derived rpnfunction type.
@- - set the deferred evaluation mode
@+ - set the immediate evaluation mode
var isvar rm rcl & = -> [] ->[]
eval ifthen thenif ifthenelse thenelseif for clear clearvar clearboth dup dupn rev rot rotn | swap \ drop \i delete dropn size stateevalis stateevalset stateevalunset load save dictadd isstring isinteger isreal iscomplex isprogram isvector string integer real ascii insert interp quit
ls tree pwd cd mv cp pushd popd depthd ispath path!
neg abs sin cos tan asin acos atan sinh cosh tanh floor ceil log log10 exp sqrt ^ factorial mod gcd
The commands are grouped by type or perspective. For example all the program commands operate on programs on a stack.
0: x push 0: x to ds2 data stack.
0: pop 0: x n: anything ... 0: n - integer pushn 0: 0: n - integer popn n-1: anything ... 0: anything 0: size2 0: n - integer 0: x eval 0: x on the stack.
For example execute a program built on
the stack.
1: number - result of test 0: x ifthen 0: x if true.
1: x 0: number - result of test thenif 0: x if true.
This is equivalent to swap ifthen
and was implemented to help the programmer
reduce another instruction.
2: number - result of test 1: a 0: b ifthenelse 0: a if true else evaluate b.
2: a 1: b 0: number - result of test thenelseif 0: a if true else evaluate b.
... 0: x clear 0: ... 0: x clearvar ... 0: x clearboth 0: 0: x dup 1: x 0: x x on the stack.
3: a 2: hat 1: 2.7 0: 3 dupn 6: a 5: hat 4: 2.7 3: 3 2: hat 1: 2.7 0: 3 n-1: n 2: ... 1: 2 0: 1 rev n-1: 1 2: ... 1: n-1 0: n 2: a 1: b 0: c rot 2: b 1: c 0: a :2 comes down.
n+1: n ... 3: 2 2: 1 1: integer n 0: integer k rotn 1: b 0: a swap 1: b 0: a | 1: a 0: b |
was added for convenience.
0: a \ 0: a drop 0: drop
the back-slash symbol \ was
implemented as the drop operator too. The drop
operator has two names.
3: hat 2: a 1: 1 0: sin 2 \i 2 delete 2: hat 1: 1 0: sin n: n 1: 1 0: n - integer dropn 0: 0: number ! 0: 0 or 1 1: x 0: string - name var 0: 0: string - name isvar 1: string 0: integer 0: string - name rm 0: 0: string - name rcl 0: integer - index rcl 0: x 0: string - name & 0: integer - index & 0: program & 0: 1: x 0: string - name =
...
a b = where a,b are integers
0: x 0: string - name ++ 0: x 0: 25 - number ++ 0: 26 0: string - name - - 0: x 0: 25 - number - - 0: 24 0: ls 0: { id=value ... } 0: tree
.
d2
h2
k2
k1
h1
d1
0: pwd 0: /d1 0: /d1/h1 path! 0: { / d1 h1 } 0: d1 - string pushd 0: d1. A directory
is a program that is a user variable.
0: popd 0: 0: depthd 0: integer 1: number 0: number + 0: number 1: and 0: and
store in 1:, delete 0:.
1: number 0: number - 0: number 1: number 0: number * 0: number 1: number 0: number / 0: number 1: number 0: number < 0: integer 1: number 0: number <= 0: integer 1: number 0: number > 0: integer 1: number 0: number >= 0: integer 1: number 0: number == 0: integer 1: program - test 0: program - body for 0: 1: program 0: n - integer forn 1: n - integer 0: program forn 0: 0 { + } 5 forn counts from 0 to 4.
1: { } - program 0: 0..3 - integer p@ 0: { } - program n: any type ... 2: any type 1: any type 0: integer n pnew 0: program n: any type ... 0: { } - program pnew! 1: ... 0: integer n - size of program pnew.
0: program prev 0: program 0: stateevalis 0: integer 0 or 1 0: stateevalset 0: @+ instead.
0: stateevalunset 0: @- instead.
3: d 2: c 1: b 0: a size 4: d ... 1: a 0: 4 0: string - filename load ... 1: anything 0: anything 0: string - filename save 0: 1: { program } 0: string - filename dictadd 0: /bin.
/bin ,
"name -> pushd Edit program...
popd . rm) the variable in /bin
does not remove the program from the dictionary,
but next time the dictionary is loaded the
program is no longer there and hence is removed.
0: x isstring 0: integer - 1 or 0 0: x isinteger 0: integer - 1 or 0 0: x isreal 0: integer - 1 or 0 0: x iscomplex 0: integer - 1 or 0 0: x isprogram 0: integer - 1 or 0 0: x isvector 0: integer - 1 or 0 0: path - string ispath 1: path - string 0: integer - 1 or 0 0: ../crap cd 0: 1: path - string 0: variable name - string mv 0: 1: path - string 0: variable name - string cp 0: 1: x - anything 0: i - integer index insert i: x ... 1: ... 0: ... 0: x - string
ascii
0: x - integer
ascii
0: x - integer or string
... 1: ... 0: i - integer index or string variable name ->
... 1: ... 0: -> value 0: i - integer index []
1: i - integer index 0: string - anything []
-> which points to
data the index pointer holds
an integer index into the stack.
->[] operator.
1: i - integer index 0: indexed pointer
->[]
1: indexed pointer 0: i - integer index
->[]
1: i - integer index 0: string
->[]
... 1: ... 0: -> value 1: { data }
0: { action }
pstream
...
2: { data }
1: { action }
0: { result }
Process a stream. The { action } is a
program that is evaluated on each element in the data
in sequential order.
eg
1: { 3 5 -7 -9 2 0 11 }
0: { dup 0 <= thenif }
pstream
produced
0: { 0 -9 -7 }
...
0: "1. 3. 5. * * "
interp
...
0: 15.000000000
The string is interpreted as a input.
For example a program could be stored as a string and interpreted when needed. Here the limitations of saving programs that use pointers could be avoided by saving the program as a string between sessions.
" { 1 k @var { k @-> #++ @rev } inc @var } "
0: ...
quit
The program is immediately terminated.
0: number string 0: - string 0: number or string integer 0: - integer 0: number or string real 0: - real 0: number neg 0: -number 0: -23 abs 0: 23 0: rpnreal sin 0: rpnreal 0: rpnreal cos 0: rpnreal 0: rpnreal tan 0: rpnreal 0: rpnreal asin 0: rpnreal 0: rpnreal acos 0: rpnreal 0: rpnreal atan 0: rpnreal 0: rpnreal sinh 0: rpnreal 0: rpnreal cosh 0: rpnreal 0: rpnreal tanh 0: rpnreal 0: 27.3 floor 0: 27.0 ceil which is equivalent
to
floor(x) + 1 except when x is an integer.
0: 27.3 ceil 0: 28.0 ceil is short for ceiling. Both floor
and ceil have no effect when the argument
is already an integer.
0: rpnreal log 0: rpnreal log and exp are each others inverses.
log refers to the natural logarithm in base e.
0: rpnreal log10 0: rpnreal 0: rpnreal, rpninteger or rpncomplex sqrt 0: rpnreal or rpncomplex 1: x - number 0: y - number ^ 0: number 0: rpninteger factorial 0: rnpinteger 1: x - integer 0: b - integer mod 0: integer 1: a - integer 0: b - integer gcd 0: integer 5: 2 4: 4 3: 8 2: 10 1: 15 0: -20 binary
5: 0000000000000000000000000000010
4: 0000000000000000000000000000100
3: 0000000000000000000000000001000
2: 0000000000000000000000000001010
1: 0000000000000000000000000001111
0: 1111111111111111111111111101100
5: 2 4: 4 3: 8 2: 10 1: 15 0: -20 oct
5: 2
4: 4
3: 10
2: 12
1: 17
0: 37777777754
5: 2 4: 4 3: 8 2: 10 1: 15 0: -20 hex
5: 2
4: 4
3: 8
2: a
1: f
0: ffffffec
5: 2
4: 4
3: 8
2: a
1: f
0: ffffffec
dec
5: 2 4: 4 3: 8 2: 10 1: 15 0: -20
0: 0000000000000000000000000000001
not
0: 1111111111111111111111111111110
1: 0000000000000000000000000101011
0: 0000000000000000000000000001001
xor
0: 0000000000000000000000000100010
1: 0000000000000000000000000101011
0: 0000000000000000000000000001001
and
0: 0000000000000000000000000001001
1: 0000000000000000000000000101011
0: 0000000000000000000000000001001
or
0: 0000000000000000000000000101011
1: 0000000000000000000000000000111
0: 0000000000000000000000000000010
shl
0: 0000000000000000000000000011100
1: 0000000000000000000000000000111
0: 0000000000000000000000000000010
shl
0: 0000000000000000000000000000001
0: (1.00000000000000000000,2.00000000000000000000)
conj
0: (1.00000000000000000000,-2.00000000000000000000)
0: (1.00000000000000000000,2.00000000000000000000)
imag
0: 2.00000000000000000000
0: (1.00000000000000000000,2.00000000000000000000)
arg
0: 1.10714871779409050297
0: rpnreal d->r 0: rpnreal 0: rpnreal r->d 0: rpnreal 1: r - rpnreal 0: theta - rpnreal p->c 0: rpncomplex 1: x - rpnreal or integer 0: y - rpnreal or integer complex 0: rpncomplex
0: (1.00000000000000000000,2.00000000000000000000)
norm
0: 5.00000000000000000000
Logic queries do not alter the argument and return true or false corresponding to 1 or 0. The function starts with is.
ispath isvector isinteger isreal
Different perspectives for algorithm development are important. Consider editing a program { } . You could edit it from the outside or step into it and edit it from inside.
The inside of a program is its data stack. Most of the functions apply to the current nodes data stack, so effectively they are from the inside perspective.
Acknowledging that the outside perspective is useful, functions were added to operate on programs only.
prev p-> p& prcl psize pnew!
pnew is an exception because it has an
outside perspective.
Let functions on programs start with p for program, but should not be confused with other commands also starting with p.
Inverses or complements are each others reverse. eg 0 ! !
is 0. Let reverse functions start with !.
path, path! pnew, pnew!
Commands that operate with directories end in d. But so not to confuse directories with the local computers file system, I have called them nodes. Notice that the command names have parallels with Unix. Indeed Unix has influenced this programming language here.
Example of directory/node commands. eg
pushd popd depthd pwd cd
Is rpn an object oriented programming language? The short answer is yes - it can be.
Firstly the standard use of rpn is as a functional language where you move around the nodes and call functions that operate on the nodes data. The functions are static in that they do something on data, - the functions themselves do not change. This is not Object Orientated Programming (henceforth abbreviated to OOP).
What OOP is is open to interpretation, so the following is my opinion on what its about, so even if you think you know what it is its worth having a listen to what I have to say.
The combining of data and function together is the single biggest advancement in software and enabled the software revolution. It forms hierachies and associates meaning by functions to data. In short functions and data together in the one unit are grouped.
Other technologies evolved (virtual functions), but the software revolution in theory could have continued without them.
Since this is a user manual I will spare you as much as possible from thinking and narrow my thoughts to OOP in rpn.
{ } as an
OOP object
The rpnprogram { } data type
meets the requirements of grouping functions
and data together.
{ } has variables or a state and a body
which is executed when the program is
behaving like a function.
The language also provides features to move into a program and effect its state by evaluating its variables and editing its stack. The variables themselves can be function and provide the objects interface.
The tree structure of rpn programs