A small, stack based language.
pila is different.
Here's a factorial macro:
:fac dup 0 = #(dup 1 - fac *) #(pop 1) if
Here's how you use it:
> 6 fac . 720
Here's a Fibonacci calculator:
:fib dup 1 <= #(1 - dup fib swap 1 - fib +) #(pop 1) if
And an example:
> 6 fib . 13
String manipulation can be neat too:
$ ./pila.io pila 20120712 > "foo" "bar" + . "foobar" > "baz" 3 * ... ["foobar", "bazbazbaz"]<= > + . "foobarbazbazbaz" > !bye goodbye
Anonymous macros open up the possibilities for meta-programming. Here's a particularly bad example:
:a #(:b a) call b
About the name
"Pila" means "stack" or "pile" in Italian. Unfortunately it's also Portuguese slang for male anatomy. I learned of this fact after I had already grown attached to the name, so I'm keeping it as "pila". However, someone in #io suggested I make the mascot an overly sexual rooster, and I might just have to do this.
Ah well, learn from my mistakes. Do more than a cursory Google search before you name a project. Or not, the results could be hilarious.
Concepts pila is a stack based language, meaning that all operations in pila are operations to manipulate the stack. Unlike languages such as FORTH, there is a single monolithic stack to manipulate, and no concept of anything such as a variable. There are however macros and anonymous macros to make your life easier.
Stack based languages operate on the concept of words, which can be seen as unary
functions--with an implicit argument being the stack. For example, the word
is a function which pushes the
3 onto the stack. The word
... prints the
stack in text form. And the word
- pops the top two elements from the stack
and subtracts the first popped element from the second. There's a more in-depth
look at words below.
Macros Macros are placeholders for a collection of words. Using macros in clever ways, you can significantly reduce the cognitive burden on yourself when writing programs.
pila recognizes and works with 3 distinct data types, which is really all you need.
pila will work with numbers (integers and floating point, autoconverted on the fly),
character strings (surrounded with
" characters), and boolean values (
pila supports 4 different bases for numbers, though they are all converted to decimal before being pushed onto the stack. To use a number in base 2, 8, or 16, prefix it with the appropriate token:
pila is also smart when it comes to types:
* change their behavior when
appropriate to work on numbers and strings!
Running pila pila is easy to run, but it requires Io. To begin a pila session, simply do:
To run a script file, do:
Builtins The following words are built in as a part of pila, and cannot be redefined:
|.||Print the top of the stack.|
|...||Print the whole stack.|
|dup||Duplicate the top of the stack.|
|cls||Empty the stack completely.|
|pop||Discard the top of the stack.|
|swap||Exchange the first two items of the stack.|
|rot||Move the third item in the stack to the top of the stack.|
|-rot||Move the top of the stack into the third position.|
|over||Push a copy of the second item in the stack to the top of the stack.|
|nip||Remove the second item in the stack.|
|tuck||Copy the top element of the stack to the third position.|
|2dup||Same as dup, but copies the top two elements.|
|2pop||Same as pop, but pops the top two elements.|
|2swap||Same as swap, but operates on pairs of elements.|
|2rot||Same as rot, but operates on pairs of elements.|
|2-rot||Same as -rot, but operates on pairs of elements.|
|2over||Same as over, but operates on pairs of elements.|
|2nip||Same as nip, but operates on pairs of elements.|
|2tuck||Same as tuck, but operates on pairs of elements.|
| Pops the top two items and pushes their sum onto the stack. If at least one of the elements is a string, + performs concatenation.
| Pops the top two items and pushes their difference onto the stack.
| Pops the top two items and pushes their product onto the stack. If one of the elements is a string, * performs string multiplication.
/ | Pops the top two items and pushes their quotient onto the stack. % | Pops the top two items and pushes their modulo onto the stack. << | Pops the top two items (m, n) from the stack and pushes (m << n) to the stack.
| Pops the top two items (m, n) from the stack and pushes (m >> n) to the stack.
= | Pops the top two items from the stack and pushes true if they're equal, false otherwise.
| Pops the top two items (m, n) from the stack and pushes true if m > n, false otherwise.
< | Pops the top two items (m, n) from the stack and pushes true if m < n, false otherwise.
= | Pops the top two items (m, n) from the stack and pushes true if m >= n, false otherwise. <= | Pops the top two items (m, n) from the stack and pushes true if m <= n, false otherwise. and | Pops the top two items (m, n) from the stack and pushes true if (m && n) == true, false otherwise. or | Pops the top two items (m, n) from the stack and pushes true if (m && n) == true, false otherwise. not | Pops the top item (n) from the stack and pushes true if n == false, false otherwise. nop | Does absolutely nothing (no operation). call | Pops the top item from the stack and attempts to evaluates it as code. if | Pops the top three items from the stack (c, t, e), and if c == true, evaluates t, otherwise evaluates e. !bye | Exits the program. !macros | Lists defined macros. !import | Pops the top item (n) from the stack and reads in the script file whose name is n.
Predefined macros (Standard Library)
The standard library has a number of macros available for your use. You can load
them into your environment by doing a
|0=||dup 0 =|
|0<||dup 0 <|
|0>||dup 0 >|
|avg||+ 2 /|
|min||2dup < #(nip) #(pop) if|
|max||2dup > #(nip) #(pop) if|
|ntimes||0> #(pop) #(over call 1 - ntimes) if|
The standard library also provides aliases for the
! respectively, as well as
for those who are used to working with FORTH.
You can define a macro by prefacing it's name with a
: character, and then
following it's name immediately by it's definition. For example, here's a
hello) that prints "Hello, world!" to the screen and then returns
the stack to it's previous state:
:hello "Hello, world!" . pop
This macro will now be called whenever the word
hello is encountered in the input.
Redefining macros You can redefine a macro in the same manner as you defined it originally. Note that you will get a warning telling you that you have redefined the macro.
Anonymous macros allow you to push a body of code onto the stack directly, instead
of pushing the literal elements. Following the
hello example from above, you
can push the call to
hello itself onto the stack by doing this:
Printing the stack, we now see this:
Meaning that the word
hello has been pushed onto the stack. This is useful for
many things, including the branching word
if, and the
call word, which pops the
top of the stack and attempts to execute it directly, such that
Is functionally equivalent to
in everything but semantics.
Anonymous macros can be used to push entire lines of input to the stack, too:
#(2 dup = . pop)
This will push the code
2 dup = . pop to the stack as a single element,
and that entire expression will be evaluated one word at a time when you do a
call on it.
Anonymous macros can also be nested:
#(1 2 3 #(- +))
Such that the first
call will push
3 to the stack, as well as
the anonymous macro
- +, and the second
call will evaluate this second macro.
Conditional execution is handled by the
if word. The
if word expects at least
3 items on the stack, in the following order from bottom to top:
[condition, else-branch, then-branch]
if word will execute the
then-branch of code if
condition is equal
true, otherwise it will execute the
else-branch. Note, both branches
are required, and both branches need to be executable, so they should be anonymous
macros. If you don't need an
else-branch for your purpose, you should provide
#(nop) as the
else-branch, meaning "do nothing.".
Here's an example:
3 1 > #("3 < 1!" . pop) #("3 > 1!" . pop) if
This will print "3 > 1!", as
3 1 > evaluated to
true, so the
Recursion is done by writing macros which reference themselves, in the non-base
case of some if-branch. There are no builtins for looping constructs, so recursion
is the order of the day for looping. Note: the standard library provides the
macro, which will perform an action a specified number of times.
On Io installations where ReadLine was compiled, the pila REPL has full ReadLine
support. This means that you can scroll through your history with your arrow
keys. The history file is saved as
Script file support
You can run a file of valid pila words by placing it's relative path as a string
on the stack, and then running the
!import word. Thanks to the ReadLine support,
typing file names and paths are tab-completed on some systems.
Comments are delimited by
//. Anything to the right of
// to the end of a line
is discarded and ignored by the parser. This works both in the REPL and in script
files. You can use this as a way to add documentation to macros that will be
visible in the repl when using the
!macros word, just be sure to keep your comments
on the same line when doing this!
Meta-programming pila allows some limited meta-programming, via the fact that both macros and anonymous macros are allowed to define new macros. For example:
> :macro1 #(:macro2 "I'm macro2, and I didn't exist when macro1 was called!" . pop) call > macro2 >> ERROR: Unknown word, ignoring: macro2 > macro1 > macro2 "I'm macro2, and I didn't exist when macro1 was called!"
You may be able to use this cleverly.
BSD. See license/license.txt for details.
- Standard library
- Fix string parsing code
- Clean it all up.
- File I/O?