This guide is intended to provide a simple introduction to the syntax and structure of programs written in Ripley. It's important to note, however, that Ripley is an experimental implementation of a language, and not a language intended for production code.
Ripley is an implementation of a variant of Forth / Postscript in Ruby.
The User's Guide is divided into chapters which follow how you might learn the language if you just started with an editor and the interpreter. As you might expect, the first thing to learn is how to "Printing things out", so that you can see what you are doing. After that you might want to learn how to "Do some math". This section will also teach you about the basic nature of Ripley, about the operand and execution stacks.
After print stuff out and doing some math, you will probably want to create some "Variables". Next, we modularize with "Making New Functions". After learning how to store stuff in memory and modularize, then next step would be to make decisions based on it, you will learn how to do that in "Ifs and Elses". Finally we loop and iterate over arrays using "Loops and stuff".
The first thing you need to do to start playing with any language is to find a way to print things out. An example of the use of the 'print' operator, which prints the item at the top of the stack out to the standard output, is shown below:
But how do we run this? There are two methods; the first is to use Ripley in an interactive mode. Where everything you type is immediately evaluated by Ripley. The example below shows how to do this:
Example 2-2. Using Ripley Interactively
Command Line
./ripley -f examples/terminal.ripley |
Program Output
Interpreter for Ripley, version 0.1 Ripley (blank line to exit) > |
The next method is to create a new text file with your Ripley commands, and then to use Ripley to execute it directly. The below shows how to do this:
Example 2-3. Using Ripley With A File
File: test.ripley
"Hello World!" print |
Command Line
./ripley -f test.ripley |
Program Output
Hello World! |
The next thing to learn is how to do some simple math, for example, write a program to print the result of 1+2. Here is an example of how to 1 and 2 together and to print the result:
What does that mean? And why does it work?
To understand why this works you need to understand the system of stack evaluation that is at the core of Ripley. Ripley has two stacks, the execution stack and the operand stack. Both stacks start empty. When a program is evaluated it is placed into the execution stack. So in our example the stacks start as:
Operand Stack: Execution Stack: 1.0 2.0 add print |
The interpreter gets the first item on the execution stack and executes it. In this case it's a simple value '1'. In the case of simple values it pushes the value onto the operand stack. So after the first evaluation the stacks look like this:
Operand Stack: 1.0 Execution Stack: 2.0 add print |
The same thing happens with the '2'.
Operand Stack: 2.0 1.0 Execution Stack: add print |
The next item is a literal 'add'. The interpreter looks literals up in the dictionaries and then executes the result. 'add' is a registered operand in the system dictionary. (More about dictionaries in a bit.) The 'add' operand pops two elements off the operand stack, adds them together pushes the result back onto the operand stack. After the evaluation of 'add' the stacks look like this:
Operand Stack: 3.0 Execution Stack: print |
Ripley then evaluates the 'print' literal, which is another operator found in the system dictionary. Print pops the operand stack and prints the result out to STDOUT. It does not push anything back onto the operand stack. After '3' is printed out by the 'print' operator the stacks look like this:
Operand Stack: Execution Stack: |
Next we do a little more math.
The next thing to lear are the other math primitives; sub, mul and div. These are the operands for subtract, multiply, and divide. They all work like add, taking two operands from the operand stack, applying a transform (adding, subtracting, multiplying or dividing) and pushing the result back onto the operand stack.
Here is an example subtraction:
An example multiplication:
An example division:
How about more complex formulae? Well, it's really a matter of creating a sequence of operands that manipulate the stack correctly. For example, how about (1+2)*10:
Is it as easy as (1+2)*10, maybe not, but it is a sytem capable of building an arbitrary simple equation.
There are only a few simple types of variables in the Ripley system.
Table 4-1. Ripley Variable Types
Simple Types | These are numbers or strings. |
Literals | These are strings that are going to be evaluated against the dictionary stack as soon as they are executed. |
Arrays | An array of items. It can include other arrays or dictionaries. |
Excutable Arrays | This is an array of elements that is to be loaded into the execution stack and evaluated. It is the Ripley equivalent of a function. |
Dictionary | An array of key value pairs that can be indexed by key. |
The variable types are discussed in more details, with examples, in the following sections.
The next sections talk in detail about each of the different variable types.
Simple types are strings or numbers that are pushed onto the operand stack when executed. Some examples are:
Table 4-2. Example Simple Types
2 | The numeric value 2.0. |
5.6 | The numeric value 5.6. |
"string value" | The string "string value". |
"string\nvalue" | The string "string value" with a carraige return between the 'string' and 'value' words. |
/stringValue | The string 'stringValue'. This is equivalent to "stringValue". |
These are string values (items that start with a alpha value) which are evaluated against the dictionary stack when they are executed.
What is the dictionary stack? The Ripley interpreter starts up with two dictionary predefined. The first is the system dictionary, which contains all of the critical operands and contants values (add, mul, div, def, ripley_version, true, false, etc.) And the user dictionary which is initially empty. The dictionary stack is the set of both the user and system dictionaries. When a literal lookup occurs the user stack is checked first, then the system stack is evaluated.
Arrays are sets of variables surrounded by brackets. An example is:
Example 4-1. Array Example 1
[ 1 2 3 ] |
This is an array of the values 1.0, 2.0 and 3.0.
Example 4-2. Array Example 2
[ 1 "hi" 3 ] |
This is an array of the values 1.0, "hi" and 3.0.
These are just like arrays, but they are delimited with curly braces. The important difference to the Ripley system is that when they are evaluated they are pushed element by element into the execution stack. Which is then interpreter by the Ripley execution engine. This becomes very important for Functions, which we get into in the next section. Examples of Executable Arrays are in the "Making New Functions" chapter.
Dictionaries are arrays that are indexed by a unique key instead of by a ascending numeric sequence. Unlike Postscript, which has special functions to build dictionaries, you can define dictionaries using the less than and greater than symbols in Ripley. An example is:
Example 4-3. Dictionary Example
< "first_name" "Jack" "last_name" "Herrington" > |
This creates a dictionary with two key value pairs. If the dictionary is accessed with "first_name" you would get "Jack" and "last_name" would get you "Herrington".
You can store any value type as an element in the user dictionary using the 'def' command. In the example below we will rewrite the '1+2' example from before using named variables.
Example 4-4. 1+2 using named variables
/a 1 def /b 2 def a b add print |
The value 1.0 is assigned to 'a' in the user dictionary using the def command, which takes a string and a value as operands. The value 2.0 to 'b'. Then the values are recalled using 'a' and 'b' as literals. Because 'a' is used as a literal it is reference in the user dictionary when it is executed and the value found in the user (or system) dictionary is then executed. In this case the value 1.0 is executed, which, because it is a simple value, is pushed onto the operand stack.
Function in Ripley are just executable arrays that have been assigned as elements in the user dictionary. To define a function we use an executable array and the 'def' operator that we used in the variable assignment section from before. Here is an example where I create a function that prints "Hello World!":
Example 5-1. Hello World function
/hello { "Hello World!" print } def hello |
Arguments to functions are implemented using the operand stack, just like every other Ripley function. Here is an example add10 function, which adds 10 to whatever you give it:
Example 5-2. Add 10 function
/add10 { 10 add } def 20 add10 print 100 add10 print |
Understanding how conditionals are implemented in Ripley requires knowing how executable arrays work, and the system dictionary values of true and false.
First is the 'if' operand, which takes two operands. The first is a boolean value, then second is an executable array. For example, this code block will always execute:
Example 6-1. Always true conditional
true { "Always prints" print } if |
The next example never prints because the boolean is negative.
Example 6-2. Always false conditional
false { "Never prints" print } if |
Well, that's great, if you want a conditional that is not conditional. So we need some comparison operators. Which is where eq, lt, le, gt, and ge come in handy. These functions take two operands off the operand stack, then push one boolean value back onto the operand stack. Here is an example of a function that prints a string if the value 'passed' in on the stack is greater than ten.
Example 6-3. Greater than ten printer
/geTenPrinter { 10 gt { "Greater than 10\n" print } if } def 5 geTenPrinter 15 geTenPrinter |
If we have an if then else type of syntax then we could implement a printer that prints out one string if it's greater than 10 and another if it's less. This is where the ifelse operator comes in handy. It takes three operands off the operand stack; the first is a boolean, the next an executable array that is executed if the boolean is true, and the next is an executable array that is executed if the value is false. (An easy way to remember which one is which is by matching the first executable array with the 'if' portion of the name and the second executable array with the 'else' portion.) Here is an example of the geTenPrint implemented using 'ifelse'.
Example 6-4. Greater than ten printer (ifelse version)
/geTenPrinter { 10 gt { "Greater than 10\n" } { "Less than 10\n" } ifelse print } def 5 geTenPrinter 15 geTenPrinter |
The last thing you need for minimal language completeness is some sort of looping construct. Ripley comes with two. The first is a 'loop' operand that continues executing an executable array until the 'exit' operator is called. The 'exit' operator sets a flag which is caught by the while when it is next executed and the while halts. Below is an example of the loop operator:
Example 7-1. Counter to 10
/count 0 def { /count count 1 add def "count = " print count print "\n" print count 10 gt { exit } if } loop |
In this example we use the 'loop' operand in concert with the 'if' and 'exit' operators to count from 0 to 10 and to print the values as we count up.
The other looping operator is the forall operator which iterates over an array, adding each item to the operand stack each time through, then executing an executable array. Below is an example of using the 'forall' operator to print out an array:
Example 7-2. Walking an array with forall
[ 1 2 3 ] { print "\n" print } forall |
Forall takes two operands off the operand stack. The first is the array to iterate over, the second is an executable array to invoke with each item. As each item is iterated over the item is pushed onto the operand stack. That's why the first 'print' works in the executable array in the previous example.
The 'exit' operand can also be used within a 'forall' construct. In the example below the code iterates through the array but breaks if the value is above 50:
Example 7-3. Using exit within forall
[ 1 2 3 10 20 30 100 200 300 ] { dup 50 gt { exit } { print "\n" print } ifelse } forall |
The following sections go into detail about the Ripley operands.
Signature
Description
Returns the second operand added to the first operand.
Examples
3.0 2.0 add 5.0 |
4.0 2.0 add 6.0 |
Signature
Description
Pushes all of the items on the array onto the operand stack, then pushes the array onto the operand stack.
Examples
Signature
Description
Returns the length of the operand stack.
Examples
1.0 2.0 3.0 count 3.0 3.0 2.0 1.0 |
Signature
Description
Dumps operand stack debugging information to STDOUT.
Examples
Signature
Description
Sets or adds an entry in the user diction for the name to the value specified.
Examples
Signature
Description
Returns the first operand divided by the second.
Examples
3.0 2.0 div 1.5 |
4.0 2.0 div 2.0 |
Signature
Description
Converts the top operand into a string and pushes it onto the stack.
Examples
Signature
Description
Duplicates the top item on the stack.
Examples
1.0 dup 1.0 1.0 |
"b" dup "b" "b" |
Signature
Description
Sets the exit flag on the current loop. Looping will terminate when the execution reaches the loop point.
Examples
Signature
Description
Iterates all of the items of an array against an executable array.
Examples
Signature
Description
Returns true if the first operand is greater than or equal to the second.
Examples
Signature
Description
For arrays this gets the item at the index specified and puts it on the stack. For keys this gets the item at the specified key.
Examples
Signature
Description
Returns true if the first operand is greater than the second.
Examples
Signature
Description
If the boolean is true then the positive executable array is invoked, otherwise the negative executable array is invoked.
Examples
Signature
Description
Pushes an array of the keys of the hash into the operand stack.
Examples
Signature
Description
Returns true if the first operand is less than or equal to the second.
Examples
Signature
Description
Returns the first operand multiplied by the second.
Examples
3.0 2.0 mul 6.0 |
4.0 2.0 mul 8.0 |
Signature
Description
Pops the first item off the stack.
Examples
1.0 2.0 pop 1.0 |
1.0 2.0 3.0 pop 2.0 1.0 |
Signature
Description
Sets the value at the specified index (or key) to the specified value.
Examples
Signature
Description
Rolls 'n' items on the stack in 'd' direction.
Examples
"a" "b" "c" 3 -1 roll "b" "c" "a" |
"a" "b" "c" 3 1 roll "c" "a" "b" |
"a" "b" "c" 3 0 roll "a" "b" "c" |
Signature
Description
Returns the second operand subtracted from the first operand.
Examples
3.0 2.0 sub 1.0 |
4.0 2.0 sub 2.0 |
Signature
Description
Takes a string as input and parses the Ripley code in the string. The resulting output is put into the operand stack as an executable array.
Examples