Skip to content

Instantly share code, notes, and snippets.

@Forkk
Last active April 7, 2020 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Forkk/40e4f9f846eff1866ae9acd9cf00bf5a to your computer and use it in GitHub Desktop.
Save Forkk/40e4f9f846eff1866ae9acd9cf00bf5a to your computer and use it in GitHub Desktop.
GRPL Guide

GRPL Guide

GRPL (short for Greenstone Reverse Polish Language) may be quite different from the languages you're used to, but its implementation is very simple. This guide aims to introduce both experienced and novice programmers to GRPL. If you've used a similar stack-based language before, you'll probably already be familiar with most of the concepts here, but you may want to skim this guide anyway to refresh your memory, and familiarize yourself with some GRPL-specific syntax.

How to Stack

Before you learn about GRPL, you should be familiar with the concept of a stack (the data structure, not a stack of items in Minecraft). If you are, go ahead and skip this section.

A stack is a data structure that GRPL uses as the primary way of passing data between commands. You can think of it like a stack of books, where you only have access to the book on the top. Each item (book) in the stack contains some value such as an integer, string, list, etc. You can add an item to the top of the stack, which we call "pushing" an element. You can also take an item from the top of the stack. We call this "popping" an element. Items will be popped from the stack in the opposite order they were pushed.

Basic Operations

The GRPL language itself is quite simple. A program is simply a sequence of statements separated by spaces. Every GRPL statement will pop some (zero or more) number values off the stack in a certain order, do some operation with them, and then push some (zero or more) values on the stack as "results". This is the language's only channel to pass values between commands and functions. This stack-based nature results in programs that almost look like they're written backwards. This is called Reverse Polish Notation, but don't let the fancy name fool you, it's actually fairly easy to understand.

Here is an example.

42 27 add print

As you may be able to tell, this program will compute 42 + 27 and then print the result to the terminal. Let's take a closer look at how this will actually execute.

This program consists of four statements. The first two are "literal" statements. A literal statement simply pushes a value on the stack, so for example the literal statement 42 will push the integer value 42 on the stack.

After the two literal statements, there are two commands, add and print. A command in GRPL is a built-in operation which can take inputs from the stack and push results back on the stack. As you might expect, the add command pops two items from the stack and pushes their sum on the stack. The print command simply pops one item off the stack and prints it to the terminal.

Writing programs in this way removes the need for parentheses or rules about order of operations, since the order is defined by the order the commands show up in the program. Take the expression (2 + 5) * (3 + 4), for example. This expression requires us to use parentheses to specify that the addition should be done first. In GRPL, we can simply write this as follows

2 5 add 3 4 add mul

Let's examine how this program will actually execute. First we have 2 5 add. This will push 2 and 5 on the stack, then run the add command, popping those values and pushing their sum. Next, we leave that result on the stack and run 3 4 add, which will do another sum. Finally, we run mul, which will take the two sums and multiply them. Note that the order of operations is specified by the order we write the commands. No parentheses necessary.

Variables

A language where you can only store values in a stack is not very useful, so GRPL also provides variables, which are similar to variables in other languages you may be familiar with. There are two statements for operating on variables, the store statement, and the load statement, written >var and <var, respectively (where "var" is your variable name).

As you might expect, a store statement (>var) will pop a value from the stack and store it in the given variable. Similarly, a load statement (<var) will push the value currently stored in the given variable.

Currently, GRPL has no concept of variable scope or local variables. All variables are global. This is planned to change in a future version.

Conditionals

Naturally, every programming language needs some form of if statement. In GRPL, an if statement starts with the keyword if, followed by a list of statements specifying the "condition", which is ended with a then keyword, followed by the body of the if statement, and then the end keyword. You can see an example below.

if 2 3 lt then
    "Yes" print
end

Here, the condition is 2 3 lt, which will push true on the stack if 2 is less than 3. The if statement will run the condition, pop the stack, and then run the body if the popped value was true.

If statements also support else clauses, which run if their associated if statement was false. You can see an example of an else clause below.

if 3 2 lt then
    "Yes" print
else
    "No" print
end

This program will print "no" to the terminal.

In addition to plain else clauses, GRPL also supports "elif" clauses, which provide an additional condition that is checked if all the conditions before them were false. The "elif" syntax is quite similar to the normal "if" syntax. See the example below.

if 3 2 lt then
    "Yes" print
elif 2 2 eq then
    "2 = 2" print
else
    "No" print
end

This program will print "2 = 2". Note that the "elif" is only checked if the if condition before it is false.

Loops

Like most languages, GRPL allows you to use loops to run some part of your code repeatedly. Currently, only one type is supported, the while loop.

While loops behave like an if statement which runs repeatedly as long as its condition continues to push true on the stack.

Here is an example:

0 >i
while <i 10 lt do
    <i print
    <i 1 add >i
end

The program above will print the numbers 0-9.

Functions

Sometimes you may want to be able to re-use part of your program in different places. As in other languages, GRPL allows you to define your own functions, which are snippets of code that can be called from other places in the program.

Functions in GRPL work a bit differently from other languages, and even slightly differently from other stack-based languages (we'll get to this in a moment). GRPL has no concept of function "arguments". Instead, your function should accept values passed to it using the stack just as the built-in commands do. When doing this, keep in mind that values on the stack are popped in the opposite order they were pushed (last in, first out).

To actually define a function, you can use the fun@name ... end syntax (where "name" is your function name, and "..." is the body). Then you can call this function using @name.

Have a look at this example, which given a stack containing a, b will compute a - 2b.

/ Define a function
fun@foo
    >b >a
    <a <b 2 mul sub
end

/ Call the function and print the result
/ Here, 42 is `a`, and 27 is `b`
42 27 @foo print

Note that values passed to the function are popped from the stack and stored in variables. Also note that the second value b is popped first, because it is meant to be pushed second before the function is called. This reverse order may be confusing at first, but with time, you should get used to it.

GRPL also has no concept of a "return value". Instead, functions simply leave one or more values on the stack, and then have the caller access those values. Keep in mind that if you want to "return" multiple values, these will also be reversed by the last in-first out nature of the stack.

Functions are Values

Where GRPL differs from many other languages like it is in how functions are actually implemented. Like many modern languages, functions are actually just values like any int, float, string, etc. In fact, the fun@foo ... end syntax shown above is just a handy syntax sugar for creating a function and storing it in a variable. It is functionally equivalent to writing fun ... end >foo. We call this fun ... end statement a "function literal", and you can use it to push anonymous function values on the stack.

Similarly, there is also an "anonymous call" statement (@), which allows you to call a function value that is on the stack.

Have a look at this (somewhat silly) example.

/ Push arguments on the stack for when we call the function later.
42 27
/ Create the function and push it on the stack instead of storing it in a variable.
fun
    >b >a
    <a <b 2 mul sub
end
/ Call the function on the stack. It will access the arguments we pushed before we
/ pushed the function.
@ print

This program will do the exact same thing as the function example you saw before, except this does it without ever storing the function in a variable.

You can use the anonymous call statement and anonymous functions to create and work with "higher order functions", which are functions that take other functions as arguments.

Comments

As you may have seen above, GRPL supports comments, which are lines that are completely ignored by the language. Any text following a single forward slash / will be ignored. If this syntax bothers you too much, you can put a second slash and pretend you're writing in some C-like language.

Some Help

GRPL provides many built-in commands to do various tasks in the language. To assist with learning the language, GRPL also provides a built-in help command, which will provide documentation about all of the commands in the language.

help is divided up into separate "topics" based on the functionality of the commands. To get a list of available help topics, simply run the help command alone with nothing on the stack.

To get information about a specific topic, run "@topic" help (note that the argument goes before the help command, since it must be read from the stack).

The topic pages will not typically describe a command fully in order to save screen space. If you want more detailed information about a specific command, you can run "command" help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment