Catasta is a weird programming language that is designed for me to have fun implementing it. It revoles around the idea of a stack machine and reverse polish notation, where operators follow the operands and act on a stack of data.
(from Wikipedia's Reverse Polish Notation)In Reverse Polish notation the operators follow their operands; for instance, to add 3 and 4, one would write "3 4 +" rather than "3 + 4". If there are multiple operations, the operator is given immediately after its second operand; so the expression written "3 − 4 + 5" in conventional infix notation would be written "3 4 − 5 +" in RPN: first subtract 4 from 3, then add 5 to that. An advantage of RPN is that it obviates the need for parentheses that are required by infix. While "3 − 4 * 5" can also be written "3 − (4 * 5)", that means something quite different from "(3 − 4) * 5". In postfix, the former would be written "3 4 5 * −", which unambiguously means "3 (4 5 *) −" which reduces to "3 20 −"; the latter would be written "3 4 - 5 *", which unambiguously means "(3 4 -) 5 *".
Interpreters of Reverse Polish notation are often stack-based; that is, operands are pushed onto a stack, and when an operation is performed, its operands are popped from a stack and its result pushed back on. So the value of postfix expression is on the top of the stack. Stacks, and therefore RPN, have the advantage of being easy to implement and very fast.
Almost everything built into the language is an operator. This includes "+", "print", and even "for". There are two types, strings and numbers, and a third, slightly odd, reference types that lazily refers to variables.
This is the Hello World program in Catasta:
"Hello World" print
The program first pushes a string onto the stack. The operator "print" then pops its one argument off the stack, prints it, and "returns" ( that is, pushes back onto the stack) nothing.
3 4 + print
This program pushes "3", pushes "4", the "+" operator pops off two items (y and x, the second to last and last item on the stack), adds them and pushes the result onto the stack, the print operator then prints the last item (x) on the stack. The stack develops as follows:
(3) -> [ 3 ]
(4) -> [3 4 ]
(+) -> [ 7 ]
(print) -> [ ]
A function is a piece of Catasta code surrounded by parantheses. They are executed using the "!" operator, can be assigned to variable names, pushed onto the stack, and used as predicates for loops and conditional expressions. This is what a function looks like:
(1 +)
Every catasta program has implied parantheses around it, so that it is a function itself. Let's look at a rather complicated program that uses nested functions, loops and variables:
# This program prints the factorials up to 50
( # new function
0 i = i print # print zero (let i be zero and print i)
1 i = i print # print one
max let # let max the first thing on the stack
( # new function
n let # let n be the first thing on the stack
( # start a new loop
n i * # put n * i onto the stack
n = # let n be that
) i n 1 - 1 -1 for # loop for every n fron n-1 to 1 by -1
n 0 + # recall n and add nothing to make it concrete
) factorial let # end of function, let it be called factorial
( # start a new loop
i 0 + factorial! # recall i and add zero to make it concrete
i = # call that i
i print # print i
) i 2 max 1 for # loop for every n fron n-1 to 1 by -1
) print_factorials_to let # end of function, called print_factorials_to
50 print_factorials_to! # put 50 onto the stack and run print_factorials_to
Mentioning a variable pushes a reference to it onto the stack. Operators will try to resolve the name to the actual value when they need one. This make indirection trivial. We have two operators to bind a value to a name, "=" and "let". The first binds the name locally only. That is, the name is only accessible inside of the current function. The "let" operator defines a global variable name. They consume two arguments, a value and a name.
12 a =
91 b =
a b ** print # this will print 1.6050678298721222e+98
There are two looping operators. The first is a "while" loop. It takes two arguments, both of which are functions. The first function is the body of the loop, the second function is a predicate. As soon as the predicates results in the last item on the stack being "0.0", it will stop running the first function. After the predicates is executed to check, it will drop the last item from the stack automatically.
10 i let
(i 1 - i = i print) (1 i <) while
The "for" operator takes five arguments: A function, a variable name to call the iteration variable, and "from", "to", and "by" values.
( i print ) i 0 10 0.5 for
As functions can be pushed onto the stack like numbers, we can also refer to the loop body indirectly:
10 max =
( i print ) my_loop =
my_loop i 0 max 0.5 for
Contitional expressions work exactly like while loops, but only execute once.
("hello world" print) (1.0) if
These operators expect two numeric arguments (either numbers or references to numbers)
+ Add
- Subtract
* Multiply
** Exponentiate (y ** x)
/ Divide
// Floor-Division
% Modulo
< 1.0 if y is less than x, else 0.0
<= 1.0 if y is less than or equal to x, else 0.0
== 1.0 if y is equal to x, else 0.0
!= 1.0 if y is not equal to x, else 0.0
>= 1.0 if y is greater than or equal to x, else 0.0
> 1.0 if y is greater than x, else 0.0
These operators expect one numeric argument
++ Increment
-- Decrement
_ Negate
These operators manipulate the stack:
dup y, x -> y, x, x
swap y, x -> x, y
rdn w, z, y, x -> x, w, z, y
rup w, z, y, x -> z, y, x, w
pop y, x -> y
string $input
number $abs
number $int
number $ceil
number $floor
number $log
number $ln
number $factorial
$pi
$e
I.e.: To calculate the factorial of a number:
50 $factorial!
The $input function expects a string, which will be the prompt for the user.
I've made a quick and dirty
Prototype Interpreter in Python. Play with it and let me know
what you think. Do note that it's probably full of bugs at this
point.
If you want syntax highlighting, set your editor to "Perl", it should
work okay.
That is all. :-)